Skip to content

pippip-tools와의 호환성

uv는 일반적인 pippip-tools 워크플로우를 대체할 수 있도록 설계되었다.

간단히 말해, 기존 pippip-tools 사용자는 패키징 워크플로우를 크게 변경하지 않고도 uv로 전환할 수 있다. 대부분의 경우, pip installuv pip install로 바꾸는 것만으로도 문제없이 작동한다.

하지만 uv는 pip의 완벽한 복제본이 아니며, 일반적인 pip 워크플로우에서 벗어날수록 동작 차이를 더 자주 마주칠 수 있다. 어떤 경우에는 이러한 차이가 의도적일 수 있고, 다른 경우에는 구현 세부사항 때문일 수 있으며, 또 다른 경우에는 버그일 수 있다.

이 문서에서는 uv와 pip 간의 알려진 차이점을 설명하고, 그 이유와 해결 방법, 그리고 향후 호환성에 대한 의도를 밝힌다.

설정 파일과 환경 변수

uv는 pip.confPIP_INDEX_URL처럼 pip 전용 설정 파일이나 환경 변수를 읽지 않는다.

다른 도구를 위한 설정 파일과 환경 변수를 읽는 것은 몇 가지 문제를 야기한다:

  1. 사용자가 포맷, 파서 등의 버그에 의존하게 되므로, 대상 도구와 버그 수준까지 호환성을 유지해야 한다.
  2. 대상 도구가 포맷을 변경하면, uv도 동일한 방식으로 변경해야 한다.
  3. 설정이 특정 버전에 종속된 경우, uv는 사용자가 어떤 버전의 도구를 사용할지 알아야 한다.
  4. 대상 도구에 없는 설정이나 구성을 uv가 도입하면, pip.conf(또는 유사 파일)가 pip에서 사용 불가능해질 수 있다.
  5. uv가 실제 동작에 영향을 미치지 않는 설정을 읽어 사용자를 혼란스럽게 할 수 있다. 많은 사용자가 uv가 다른 도구를 위한 설정 파일을 읽을 것이라고 예상하지 않는다.

대신, uv는 UV_INDEX_URL 같은 자체 환경 변수를 지원한다. 또한 uv.toml 파일이나 pyproject.toml[tool.uv.pip] 섹션을 통해 지속적인 구성을 지원한다. 자세한 내용은 설정 파일을 참고한다.

사전 릴리스 호환성

기본적으로 uv는 다음과 같은 두 가지 경우에 의존성 해결 과정에서 사전 릴리스 버전을 허용한다:

  1. 패키지가 직접 의존성이고, 버전 마커에 사전 릴리스 지정자가 포함된 경우 (예: flask>=2.0.0rc1).
  2. 패키지의 모든 게시된 버전이 사전 릴리스인 경우.

전이적 사전 릴리스로 인해 의존성 해결이 실패하면, uv는 사용자에게 --prerelease allow 옵션을 사용해 다시 실행하라는 메시지를 표시한다. 이 옵션을 사용하면 모든 의존성에 대해 사전 릴리스를 허용한다.

또는 전이적 의존성을 requirements.in 파일에 사전 릴리스 지정자와 함께 추가해 특정 의존성에 대한 사전 릴리스 지원을 선택할 수 있다 (예: flask>=2.0.0rc1).

요약하면, uv는 사전 릴리스를 허용할지 여부를 미리 알아야 한다. 반면 pip은 해결기가 관련 지정자를 어떤 순서로 만나느냐에 따라 전이적 의존성에서 사전 릴리스 식별자를 일부 존중할 수 있다 (#1641).

사전 릴리스는 모델링하기 매우 까다롭고, 패키징 도구에서 버그의 빈번한 원인이 된다. 참조 구현체로 여겨지는 pip조차도 사전 릴리스 처리에 대해 여러 가지 미해결 문제가 있다 (#12469, #12470, #40505 등). uv의 사전 릴리스 처리는 의도적으로 제한적이며, 의도적으로 사용자가 사전 릴리스를 명시적으로 선택하도록 요구한다. 이는 정확성을 보장하기 위함이다.

앞으로 uv는 전이적 의존성에서 사전 릴리스 식별자를 지원할 수도 있다. 그러나 이는 Python 패키징 사양의 발전에 달려 있을 가능성이 크다. 기존 PEP는 "의존성 해결"을 다루지 않으며, 대신 단일 버전 지정자에 대한 동작에 초점을 맞추고 있다. 따라서 패키징 생태계에서 사전 릴리스에 대한 올바르고 의도된 동작에 대해 해결되지 않은 문제가 남아 있다.

여러 인덱스에 존재하는 패키지

uv와 pip 모두에서 사용자는 특정 패키지의 사용 가능한 버전을 검색할 여러 패키지 인덱스를 지정할 수 있다. 하지만 uv와 pip은 여러 인덱스에 존재하는 패키지를 처리하는 방식에서 차이가 있다.

예를 들어, 한 회사가 내부용 requests 패키지를 프라이빗 인덱스(--extra-index-url)에 배포하면서도 기본적으로 PyPI에서 패키지를 설치할 수 있도록 허용한다고 가정해 보자. 이 경우, 프라이빗 requests는 PyPI의 공개 requests와 충돌할 수 있다.

uv는 여러 인덱스에서 패키지를 검색할 때, 인덱스를 순서대로 반복하며(--extra-index-url을 기본 인덱스보다 우선시함), 일치하는 패키지를 찾는 즉시 검색을 중단한다. 이는 패키지가 여러 인덱스에 존재할 경우, uv가 해당 패키지를 포함하는 첫 번째 인덱스에 있는 버전들만 후보로 고려한다는 것을 의미한다.

반면 pip은 모든 인덱스에서 후보 버전을 결합하고, 결합된 집합에서 최적의 버전을 선택한다. 하지만 pip은 인덱스 검색 순서에 대해 어떤 보장도 하지 않으며, 패키지가 이름과 버전 기준으로 고유하다고 가정한다. 심지어 이는 인덱스 간에서도 마찬가지다.

uv의 동작 방식은 패키지가 내부 인덱스에 존재할 경우, 항상 내부 인덱스에서 설치하고 PyPI에서는 설치하지 않도록 설계되었다. 이는 "의존성 혼동" 공격을 방지하기 위한 목적이다. 이 공격은 공격자가 내부 패키지와 동일한 이름의 악성 패키지를 PyPI에 배포하여 악성 패키지가 내부 패키지 대신 설치되도록 유도하는 방식이다. 예를 들어, 2022년 12월 발생한 torchtriton 공격이 대표적인 사례다.

v0.1.39 버전부터 사용자는 --index-strategy 커맨드라인 옵션이나 UV_INDEX_STRATEGY 환경 변수를 통해 pip 스타일의 동작을 선택할 수 있다. 이 옵션은 다음과 같은 값을 지원한다:

  • first-index (기본값): 모든 인덱스에서 각 패키지를 검색하되, 패키지를 포함하는 첫 번째 인덱스의 버전들만 후보로 고려한다. --extra-index-url 인덱스를 기본 인덱스 URL보다 우선시한다.
  • unsafe-first-match: 모든 인덱스에서 각 패키지를 검색하되, 다른 인덱스에 더 새로운 버전이 있더라도 첫 번째 인덱스의 호환 가능한 버전을 우선시한다.
  • unsafe-best-match: 모든 인덱스에서 각 패키지를 검색하고, 후보 버전들의 결합된 집합에서 최적의 버전을 선택한다.

unsafe-best-matchpip의 동작과 가장 유사하지만, "의존성 혼동" 공격의 위험에 노출될 수 있다.

uv는 또한 특정 패키지를 전용 인덱스에 고정하는 기능도 지원한다(Indexes 참조). 이를 통해 특정 패키지는 항상 지정된 인덱스에서 설치된다.

PEP 517 빌드 격리

uv는 기본적으로 PEP 517 빌드 격리를 사용한다. 이는 pip install --use-pep517과 유사하며, pypa/build를 따르고 향후 pip이 PEP 517 빌드를 기본으로 전환할 예정임을 고려한 것이다(pypa/pip#9175).

빌드 시 의존성이 누락되어 패키지 설치에 실패하면, 먼저 패키지의 최신 버전을 사용해 보길 권한다. 문제가 지속되면 패키지 관리자에게 이슈를 제기하여 올바른 PEP 517 빌드 시 의존성을 명시하도록 요청하는 것이 좋다.

빌드 격리를 우회하려면, 패키지의 빌드 의존성을 미리 설치한 후 --no-build-isolation 옵션과 함께 uv pip install을 실행한다. 예를 들면 다음과 같다:

uv pip install wheel && uv pip install --no-build-isolation biopython==1.77

PEP 517 빌드 격리에서 실패하는 것으로 알려진 패키지 목록은 #2252에서 확인할 수 있다.

전이적 URL 의존성

uv는 URL 의존성(예: ruff @ https://...)에 대한 퍼스트클래스 지원을 포함하지만, 전이적 URL 의존성을 처리하는 방식에서 pip와 두 가지 차이점이 있다.

첫째, uv는 URL이 아닌 의존성이 URL 의존성을 해결 과정에 도입하지 않는다고 가정한다. 다시 말해, 레지스트리에서 가져온 의존성 자체가 URL에 의존하지 않는다고 간주한다. 만약 URL이 아닌 의존성이 URL 의존성을 도입하면, uv는 해결 과정에서 해당 URL 의존성을 거부한다. (참고로 PyPI는 공개된 패키지가 URL 의존성을 가지는 것을 허용하지 않는다. 다른 레지스트리는 더 관대할 수 있다.)

둘째, 직접 URL 의존성을 사용하여 제약 조건(--constraint) 또는 오버라이드(--override)가 정의되고, 제약된 패키지가 자체적으로 직접 URL 의존성을 가지고 있다면, uv는 해결 과정에서 해당 전이적 직접 URL 의존성을 거부할 수 있다. 단, 이는 URL이 입력 요구 사항 집합의 다른 곳에서 참조되지 않는 경우에 한한다.

uv가 전이적 URL 의존성을 거부하면, 가장 좋은 방법은 관련 pyproject.toml 또는 requirement.in 파일에 URL 의존성을 직접 의존성으로 추가하는 것이다. 위에서 언급한 제약 조건은 직접 의존성에는 적용되지 않기 때문이다.

기본적으로 가상 환경 사용

uv pip installuv pip sync는 기본적으로 가상 환경과 함께 동작하도록 설계되었다.

구체적으로, uv는 항상 현재 활성화된 가상 환경에 패키지를 설치하거나, 현재 디렉터리나 상위 디렉터리에서 .venv라는 이름의 가상 환경을 찾아 설치한다(활성화되지 않았더라도).

이는 pip와 다르다. pip는 가상 환경이 활성화되지 않으면 전역 환경에 패키지를 설치하며, 비활성화된 가상 환경을 찾지 않는다.

uv에서는 --python /path/to/python 옵션을 통해 Python 실행 파일 경로를 제공하거나, --system 플래그를 사용해 시스템 Python에 설치할 수 있다. 이 플래그는 pip처럼 PATH에서 찾은 첫 번째 Python 인터프리터에 설치한다.

즉, uv는 기본 동작을 뒤집어 시스템 Python에 설치하려면 명시적으로 옵트인해야 한다. 이는 시스템을 손상시킬 수 있고 복잡한 문제를 일으킬 수 있으므로, 제한된 상황에서만 사용해야 한다.

자세한 내용은 "임의의 Python 환경 사용하기"를 참고한다.

의존성 해결 전략

주어진 의존성 명세에 대해 설치할 패키지의 '정확한' 집합은 보통 하나로 정해지지 않는다. 대신, 명세를 만족하는 여러 유효한 패키지 집합이 존재한다.

pip와 uv 모두 설치할 패키지의 정확한 집합에 대해 어떤 보장도 하지 않는다. 단지 해결 결과가 일관적이고 결정적이며 명세를 준수할 것만을 보장한다. 따라서 어떤 경우에는 pip와 uv가 서로 다른 해결 결과를 내놓을 수 있지만, 두 결과 모두 동등하게 유효하다.

예를 들어, 다음을 살펴보자:

requirements.in
starlette
fastapi

이 글을 쓰는 시점에서 starlette의 최신 버전은 0.37.2이고, fastapi의 최신 버전은 0.110.0이다. 그러나 fastapi==0.110.0starlette에 의존하며, 상한을 지정한다: starlette>=0.36.3,<0.37.0.

만약 해결자가 starlette의 최신 버전을 우선시한다면, starlette에 대한 상한을 제외한 이전 버전의 fastapi를 사용해야 한다. 실제로 이 경우 fastapi==0.1.17로 되돌아가야 한다:

requirements.txt
# This file was autogenerated by uv via the following command:
#    uv pip compile requirements.in
annotated-types==0.6.0
    # via pydantic
anyio==4.3.0
    # via starlette
fastapi==0.1.17
idna==3.6
    # via anyio
pydantic==2.6.3
    # via fastapi
pydantic-core==2.16.3
    # via pydantic
sniffio==1.3.1
    # via anyio
starlette==0.37.2
    # via fastapi
typing-extensions==4.10.0
    # via
    #   pydantic
    #   pydantic-core

반대로, 해결자가 fastapi의 최신 버전을 우선시한다면, starlette의 상한을 만족하는 이전 버전을 사용해야 한다. 실제로 이 경우 starlette==0.36.3으로 되돌아가야 한다:

requirements.txt
# This file was autogenerated by uv via the following command:
#    uv pip compile requirements.in
annotated-types==0.6.0
    # via pydantic
anyio==4.3.0
    # via starlette
fastapi==0.110.0
idna==3.6
    # via anyio
pydantic==2.6.3
    # via fastapi
pydantic-core==2.16.3
    # via pydantic
sniffio==1.3.1
    # via anyio
starlette==0.36.3
    # via fastapi
typing-extensions==4.10.0
    # via
    #   fastapi
    #   pydantic
    #   pydantic-core

uv의 해결 결과가 pip와 바람직하지 않은 방식으로 다를 경우, 이는 종종 명세가 너무 느슨하다는 신호이다. 사용자는 명세를 더 엄격하게 조정하는 것을 고려해야 한다. 예를 들어, starlettefastapi의 경우, 사용자는 fastapi>=0.110.0을 요구할 수 있다.

pip check

현재 uv pip check는 다음과 같은 진단 정보를 제공한다:

  • 패키지에 METADATA 파일이 없거나, METADATA 파일을 파싱할 수 없는 경우
  • 패키지의 Requires-Python이 현재 실행 중인 인터프리터의 Python 버전과 일치하지 않는 경우
  • 패키지가 설치되지 않은 패키지에 의존하는 경우
  • 패키지가 설치된 패키지에 의존하지만, 호환되지 않는 버전인 경우
  • 가상 환경에 동일한 패키지의 여러 버전이 설치된 경우

어떤 경우에는 uv pip checkpip check에서 제공하지 않는 진단 정보를 표시하기도 하고, 그 반대의 경우도 있다. 예를 들어, pip check는 현재 환경에 동일한 패키지의 여러 버전이 설치된 경우 경고를 표시하지 않지만, uv pip check는 이를 경고한다.

--user 옵션과 user 설치 스킴

uv는 --user 플래그를 지원하지 않는다. 이 플래그는 user 설치 스킴을 기반으로 패키지를 설치한다. 대신, 패키지 설치를 분리하기 위해 가상 환경 사용을 권장한다.

또한 pip는 사용자가 대상 디렉토리에 쓰기 권한이 없을 때 user 설치 스킴으로 대체한다. 이는 시스템 Python에 설치할 때 일부 시스템에서 발생하는 상황이다. uv는 이러한 대체 동작을 구현하지 않는다.

자세한 내용은 #2077을 참고한다.

--only-binary 옵션 강제 적용

--only-binary 인자는 사전 빌드된 바이너리 배포판만 설치하도록 제한한다. --only-binary :all:을 사용하면 pip와 uv 모두 PyPI 및 다른 레지스트리에서 소스 배포판을 빌드하지 않는다.

그러나 직접 URL로 제공되는 의존성(예: uv pip install https://...)의 경우, pip는 --only-binary를 강제로 적용하지 않는다. 따라서 pip는 이러한 모든 패키지에 대해 소스 배포판을 빌드한다.

반면 uv는 직접 URL 의존성에 대해 --only-binary를 강제로 적용한다. 단, 한 가지 예외가 있다. uv pip install https://... --only-binary flask와 같은 경우, uv는 패키지 이름을 미리 추론할 수 없을 때 주어진 URL에서 소스 배포판을 빌드한다. 이는 uv가 메타데이터를 빌드하지 않고서는 패키지가 "허용"되는지 여부를 판단할 수 없기 때문이다.

pip와 uv 모두 --only-binary가 제공되더라도 편집 가능한 요구사항(editables requirements)을 빌드하고 설치하는 것을 허용한다. 예를 들어, uv pip install -e . --only-binary :all:은 허용된다.

--no-binary 옵션 강제 적용

--no-binary 인자는 소스 배포본만 설치하도록 제한한다. --no-binary를 사용하면 uv는 미리 빌드된 바이너리 배포본의 설치를 거부하지만, 로컬 캐시에 이미 존재하는 바이너리 배포본은 재사용한다.

또한 pip와 달리, uv의 리졸버는 --no-binary가 적용된 경우에도 미리 빌드된 바이너리 배포본의 메타데이터를 읽는다.

manylinux_compatible 강제 적용

PEP 600은 Python 배포자가 _manylinux 표준 라이브러리 모듈에 manylinux_compatible 함수를 정의함으로써 manylinux 호환성을 선택적으로 적용할 수 있는 메커니즘을 설명한다.

uv는 manylinux_compatible을 존중하지만, 현재 glibc 버전에 대해서만 테스트를 수행하며, manylinux_compatible의 반환 값을 전역적으로 적용한다.

즉, manylinux_compatibleTrue를 반환하면 uv는 해당 시스템을 manylinux 호환으로 간주한다. 반대로 False를 반환하면 manylinux 비호환으로 간주하며, 모든 glibc 버전에 대해 manylinux_compatible을 호출하지 않는다.

이 접근 방식은 명세의 완전한 구현은 아니지만, no-manylinux와 같은 일반적인 포괄적인 manylinux_compatible 구현과 호환된다:

from __future__ import annotations
manylinux1_compatible = False
manylinux2010_compatible = False
manylinux2014_compatible = False


def manylinux_compatible(*_, **__):  # PEP 600
    return False

바이트코드 컴파일

pip와 달리 uv는 기본적으로 설치 과정에서 .py 파일을 .pyc 파일로 컴파일하지 않는다. 즉, uv는 __pycache__ 디렉터리를 생성하거나 채우지 않는다. 설치 시 바이트코드 컴파일을 활성화하려면 uv pip install 또는 uv pip sync 명령에 --compile-bytecode 플래그를 추가하거나, UV_COMPILE_BYTECODE 환경 변수를 1로 설정한다.

바이트코드 컴파일을 건너뛰는 것은 특정 워크플로우에서 바람직하지 않을 수 있다. 예를 들어, Docker 빌드에서는 시작 시간을 단축하기 위해 바이트코드 컴파일을 활성화할 것을 권장한다(빌드 시간이 증가하는 대신).

바이트코드 컴파일은 Python 인터프리터가 발생시키는 다양한 경고를 억제한다. 따라서 드물게 uv로 설치한 Python 코드를 실행할 때 SyntaxWarning이나 DeprecationWarning 메시지가 표시될 수 있으며, 이는 pip를 사용할 때는 나타나지 않는다. 이러한 경고는 유효하지만, 일반적으로 바이트코드 컴파일 과정에서 숨겨진다. 이 경고는 무시하거나, 상위 수준에서 수정하거나, uv에서 바이트코드 컴파일을 활성화하여 비슷하게 억제할 수 있다.

엄격성과 스펙 준수

uv는 pip보다 더 엄격한 기준을 적용한다. pip가 설치를 허용하는 패키지도 uv는 거부하는 경우가 있다. 예를 들어, uv는 잘못된 URL 조각이 포함된 HTML 인덱스를 거부하지만, pip는 이를 무시한다.

특정 스펙 준수 문제가 알려진 유명 패키지의 경우, uv는 관대하게 처리하기도 한다.

스펙 위반으로 인해 pip가 설치하는 패키지를 uv가 거부한다면, 먼저 해당 패키지의 최신 버전을 설치해 보는 것이 좋다. 그래도 실패할 경우, 패키지 관리자에게 문제를 보고해야 한다.

pip 커맨드라인 옵션과 하위 명령어

uv는 pip의 모든 커맨드라인 옵션과 하위 명령어를 완벽히 지원하지는 않지만, 상당 부분을 지원한다.

지원되지 않는 옵션과 하위 명령어는 사용자 요구와 구현 복잡도를 기준으로 우선순위가 정해지며, 일반적으로 개별 이슈로 추적된다. 예를 들어:

지원되지 않는 옵션이나 하위 명령어를 발견하면, 해당 이슈가 이미 보고되었는지 이슈 트래커를 검색해 보길 바란다. 아직 보고되지 않았다면 새 이슈를 열어도 좋다. 기존 이슈에 관심을 표시하고 싶다면, 해당 이슈에 '좋아요'를 눌러도 된다.

레지스트리 인증

uv는 --keyring-provider에 대해 pipauto 또는 import 옵션을 지원하지 않는다. 현재는 subprocess 옵션만 사용할 수 있다.

pip과 달리, uv는 기본적으로 키링 인증을 활성화하지 않는다.

pip과 달리, uv는 HTTP 401 응답을 받기 전까지 기다리지 않는다. uv는 자격 증명이 있는 호스트에 대해 모든 요청에 인증 정보를 첨부한다.

egg 지원

uv는 pip에서 레거시 또는 더 이상 사용되지 않는 기능을 지원하지 않는다. 예를 들어, uv는 .egg 스타일 배포를 지원하지 않는다.

하지만 uv는 부분적으로 (1) Docker 이미지와 Conda 환경에서 가끔 발견되는 .egg-info 스타일 배포와 (2) 레거시 편집 가능한 .egg-link 스타일 배포를 지원한다.

구체적으로, uv는 새로운 .egg-info 또는 .egg-link 스타일 배포를 설치하지는 않지만, 해결 과정에서 기존에 존재하는 이러한 배포를 인식하고, uv pip listuv pip freeze로 이를 나열하며, uv pip uninstall로 제거할 수 있다.

빌드 제약 조건

--constraint(또는 UV_CONSTRAINT)를 통해 제약 조건을 제공할 때, uv는 빌드 의존성을 해결할 때(예: 소스 배포판을 빌드할 때) 해당 제약 조건을 적용하지 않는다. 대신, 전용 --build-constraint(또는 UV_BUILD_CONSTRAINT) 설정을 통해 빌드 제약 조건을 제공해야 한다.

한편 pip은 PIP_CONSTRAINT를 통해 지정된 경우 빌드 의존성에 제약 조건을 적용하지만, 커맨드라인에서 --constraint를 통해 제공된 경우에는 적용하지 않는다.

예를 들어, setuptools에 빌드 의존성이 있는 패키지를 빌드할 때 setuptools 60.0.0을 사용하도록 보장하려면 --constraint 대신 --build-constraint를 사용한다.

pip compile 기본 동작

pip compilepip-tools의 기본 동작에는 몇 가지 작지만 주목할 만한 차이가 있다.

기본적으로 uv는 컴파일된 요구사항을 출력 파일에 자동으로 저장하지 않는다. 대신, 사용자가 -o 또는 --output-file 옵션을 통해 출력 파일을 명시적으로 지정해야 한다.

uv는 기본적으로 컴파일된 요구사항을 출력할 때 extras를 제거한다. 즉, uv는 --strip-extras를 기본값으로 사용하지만, pip-compile--no-strip-extras를 기본값으로 사용한다. pip-compile은 다음 주요 릴리스(v8.0.0)에서 이 기본값을 변경할 예정이며, 그때부터 두 도구 모두 --strip-extras를 기본값으로 사용하게 된다. uv에서 extras를 유지하려면 uv pip compile 명령에 --no-strip-extras 플래그를 추가한다.

기본적으로 uv는 출력 파일에 인덱스 URL을 기록하지 않지만, pip-compile은 기본값(PyPI)과 일치하지 않는 --index-url 또는 --extra-index-url을 출력한다. 출력 파일에 인덱스 URL을 포함하려면 uv pip compile 명령에 --emit-index-url 플래그를 추가한다. pip-compile과 달리, uv는 --emit-index-url이 전달되면 기본 인덱스 URL을 포함한 모든 인덱스 URL을 출력한다.

requires-python 적용 규칙

의존성의 requires-python 범위를 평가할 때, uv는 하한만 고려하고 상한은 완전히 무시한다. 예를 들어, >=3.8, <4>=3.8로 처리된다. requires-python의 상한을 존중하면 이론적으로는 정확하지만 실제로는 잘못된 해결책을 도출할 수 있다. 예를 들어, 리졸버는 상한이 없는 처음 발표된 버전으로 되돌아가는 경우가 있다 (참고: Requires-Python 상한).

requires-python 지정자에 대해 Python 버전을 평가할 때, uv는 후보 버전을 메이저, 마이너, 패치 구성 요소로 잘라내고, 프리릴리즈나 포스트릴리즈 식별자 등을 무시한다.

예를 들어, requires-python: >=3.13을 선언한 프로젝트는 Python 3.13.0b1을 허용한다. 3.13.0b1은 엄밀히 말하면 3.13보다 크지 않지만, 프리릴리즈 식별자를 생략하면 3.13보다 크다고 판단한다.

이 방식은 PEP 440을 엄격히 준수하지는 않지만, pip와 일관된 동작을 보인다.

패키지 우선순위

주어진 요구사항에 대해 보통 여러 가지 해결책이 존재하며, 리졸버는 이들 중 하나를 선택해야 한다. uv의 리졸버와 pip의 리졸버는 서로 다른 패키지 우선순위 체계를 가지고 있다. 두 리졸버 모두 사용자가 제공한 순서를 우선순위 중 하나로 사용하지만, pip는 uv에는 없는 추가적인 우선순위를 가지고 있다. 따라서 uv는 pip보다 사용자 순서 변경의 영향을 더 크게 받는다.

예를 들어, uv pip install foo barfoo의 최신 버전을 bar보다 우선시하며, 이는 uv pip install bar foo와 다른 해결 결과를 가져올 수 있다. 마찬가지로, 이 동작은 uv pip compile의 입력 파일에서 요구사항의 순서에도 적용된다.