Skip to content

프로젝트 설정

파이썬 버전 요구사항

프로젝트는 pyproject.toml 파일의 project.requires-python 필드에서 지원하는 파이썬 버전을 명시할 수 있다.

requires-python 값을 설정하는 것을 권장한다:

pyproject.toml
[project]
name = "example"
version = "0.1.0"
requires-python = ">=3.12"

파이썬 버전 요구사항은 프로젝트에서 허용되는 파이썬 문법을 결정하고, 의존성 버전 선택에 영향을 미친다(의존성도 동일한 파이썬 버전 범위를 지원해야 한다).

진입점

진입점은 설치된 패키지가 인터페이스를 제공하기 위해 사용하는 공식적인 용어다. 이는 다음과 같은 것들을 포함한다:

중요

진입점 테이블을 사용하려면 빌드 시스템이 정의되어 있어야 한다.

커맨드라인 인터페이스

프로젝트는 pyproject.toml 파일의 [project.scripts] 테이블에 커맨드라인 인터페이스(CLI)를 정의할 수 있다.

예를 들어, example 모듈의 hello 함수를 호출하는 hello 커맨드를 선언하려면 다음과 같이 작성한다:

pyproject.toml
[project.scripts]
hello = "example:hello"

이제 콘솔에서 해당 커맨드를 실행할 수 있다:

$ uv run hello

그래픽 사용자 인터페이스

프로젝트는 pyproject.toml 파일의 [project.gui-scripts] 테이블에서 그래픽 사용자 인터페이스(GUI)를 정의할 수 있다.

중요

이는 커맨드라인 인터페이스와 윈도우에서만 차이가 있다. 윈도우에서는 GUI 실행 파일로 감싸져 콘솔 없이 시작할 수 있다. 다른 플랫폼에서는 동일하게 동작한다.

예를 들어, example 모듈의 app 함수를 호출하는 hello라는 커맨드를 정의하려면 다음과 같이 작성한다:

pyproject.toml
[project.gui-scripts]
hello = "example:app"

플러그인 진입점 설정

프로젝트는 pyproject.toml 파일의 [project.entry-points] 테이블에 플러그인 탐색을 위한 진입점을 정의할 수 있다.

예를 들어, example-plugin-a 패키지를 example의 플러그인으로 등록하려면 다음과 같이 설정한다:

pyproject.toml
[project.entry-points.'example.plugins']
a = "example_plugin_a"

그리고 example에서 플러그인을 로드하려면 아래와 같이 코드를 작성한다:

example/__init__.py
from importlib.metadata import entry_points

for plugin in entry_points(group='example.plugins'):
    plugin.load()

Note

group 키는 임의의 값을 가질 수 있으며, 패키지 이름이나 "plugins"를 포함할 필요는 없다. 하지만 다른 패키지와의 충돌을 방지하기 위해 패키지 이름으로 네임스페이스를 지정하는 것이 좋다.

빌드 시스템

빌드 시스템은 프로젝트를 어떻게 패키징하고 설치할지 결정한다. 프로젝트는 pyproject.toml 파일의 [build-system] 테이블에서 빌드 시스템을 선언하고 설정할 수 있다.

uv는 빌드 시스템의 존재 여부를 통해 프로젝트가 가상 환경에 설치해야 할 패키지를 포함하는지 판단한다. 빌드 시스템이 정의되지 않으면, uv는 프로젝트 자체를 빌드하거나 설치하려고 시도하지 않고, 단지 의존성만 설치한다. 빌드 시스템이 정의되면, uv는 프로젝트를 빌드하고 프로젝트 환경에 설치한다.

uv init 명령에 --build-backend 옵션을 제공하면 적절한 레이아웃으로 패키지된 프로젝트를 생성할 수 있다. --package 옵션을 제공하면 기본 빌드 시스템으로 패키지된 프로젝트를 생성한다.

참고

빌드 시스템 정의 없이는 uv가 현재 프로젝트를 빌드하거나 설치하지 않지만, 다른 패키지에서는 [build-system] 테이블이 필수는 아니다. 레거시 이유로, 빌드 시스템이 정의되지 않으면 setuptools.build_meta:__legacy__를 사용해 패키지를 빌드한다. 여러분이 의존하는 패키지는 빌드 시스템을 명시적으로 선언하지 않았더라도 여전히 설치할 수 있다. 마찬가지로, 로컬 패키지를 의존성으로 추가하거나 uv pip로 설치하면, uv는 항상 빌드하고 설치하려고 시도한다.

빌드 시스템 옵션

빌드 시스템은 다음과 같은 기능을 지원한다:

  • 배포판에 포함하거나 제외할 파일 관리
  • 편집 가능한 설치 동작 설정
  • 동적 프로젝트 메타데이터 처리
  • 네이티브 코드 컴파일
  • 공유 라이브러리 벤더링

이 기능들을 설정하려면 선택한 빌드 시스템의 문서를 참고한다.

프로젝트 패키징

빌드 시스템에서 논의한 바와 같이, Python 프로젝트는 설치를 위해 빌드해야 한다. 이 과정을 일반적으로 "패키징"이라고 부른다.

다음과 같은 경우에는 패키지가 필요할 수 있다:

  • 프로젝트에 커맨드를 추가할 때
  • 프로젝트를 다른 사람에게 배포할 때
  • srctest 레이아웃을 사용할 때
  • 라이브러리를 작성할 때

반면 다음과 같은 경우에는 패키지가 필요하지 않을 수 있다:

  • 스크립트를 작성할 때
  • 간단한 애플리케이션을 빌드할 때
  • 플랫 레이아웃을 사용할 때

uv는 일반적으로 빌드 시스템의 선언을 통해 프로젝트를 패키징할지 여부를 결정한다. 하지만 uv는 tool.uv.package 설정을 통해 이 동작을 재정의할 수도 있다.

tool.uv.package = true로 설정하면 프로젝트를 강제로 빌드하고 프로젝트 환경에 설치한다. 빌드 시스템이 정의되지 않은 경우, uv는 setuptools 레거시 백엔드를 사용한다.

tool.uv.package = false로 설정하면 프로젝트 패키지를 빌드하고 설치하지 않도록 강제한다. uv는 프로젝트와 상호작용할 때 선언된 빌드 시스템을 무시한다.

프로젝트 환경 경로 설정

UV_PROJECT_ENVIRONMENT 환경 변수를 사용해 프로젝트 가상 환경 경로를 설정할 수 있다. 기본값은 .venv이다.

상대 경로를 지정하면 워크스페이스 루트를 기준으로 경로를 해석한다. 절대 경로를 지정하면 그대로 사용하며, 환경을 위한 하위 디렉터리를 생성하지 않는다. 지정한 경로에 환경이 없으면 uv가 새로 생성한다.

이 옵션을 사용해 시스템 Python 환경에 쓸 수 있지만 권장하지 않는다. uv sync는 기본적으로 환경에서 불필요한 패키지를 제거하므로 시스템을 불안정한 상태로 만들 수 있다.

시스템 환경을 대상으로 하려면 UV_PROJECT_ENVIRONMENT를 Python 설치 경로의 prefix로 설정한다. 예를 들어 Debian 기반 시스템에서는 일반적으로 /usr/local이다:

$ python -c "import sysconfig; print(sysconfig.get_config_var('prefix'))"
/usr/local

이 환경을 대상으로 하려면 UV_PROJECT_ENVIRONMENT=/usr/local을 export한다.

중요

절대 경로를 지정하고 이 설정을 여러 프로젝트에서 사용하면 각 프로젝트의 호출로 인해 환경이 덮어쓰여진다. 이 설정은 CI나 Docker 이미지에서 단일 프로젝트에만 사용하는 것을 권장한다.

참고

기본적으로 uv는 프로젝트 작업 중 VIRTUAL_ENV 환경 변수를 읽지 않는다. VIRTUAL_ENV가 프로젝트 환경 경로와 다르게 설정되어 있으면 경고를 표시한다. --active 플래그를 사용해 VIRTUAL_ENV를 존중하도록 선택할 수 있다. --no-active 플래그를 사용해 경고를 무시할 수 있다.

제한된 환경 지원

여러분의 프로젝트가 특정 플랫폼이나 파이썬 버전만 지원한다면, environments 설정을 통해 지원할 플랫폼을 제한할 수 있다. 이 설정은 PEP 508 환경 마커의 리스트를 인자로 받는다. 예를 들어, lockfile을 macOS와 Linux로 제한하고 Windows는 제외하려면 다음과 같이 설정한다:

pyproject.toml
[tool.uv]
environments = [
    "sys_platform == 'darwin'",
    "sys_platform == 'linux'",
]

더 자세한 내용은 해상도 문서를 참고한다.

필수 환경 설정

프로젝트에서 특정 플랫폼이나 Python 버전을 반드시 지원해야 한다면, required-environments 설정을 통해 해당 플랫폼을 필수로 지정할 수 있다. 예를 들어, 프로젝트가 Intel macOS를 지원해야 한다면 다음과 같이 설정한다:

pyproject.toml
[tool.uv]
required-environments = [
    "sys_platform == 'darwin' and platform.machine() == 'x86_64'",
]

required-environments 설정은 소스 배포판을 제공하지 않는 패키지(예: PyTorch)에만 관련이 있다. 이러한 패키지는 해당 패키지가 제공하는 미리 빌드된 바이너리 배포판(wheels)이 지원하는 환경에서만 설치할 수 있다.

자세한 내용은 해결 문서를 참고한다.

빌드 격리

기본적으로 uv는 PEP 517에 따라 모든 패키지를 격리된 가상 환경에서 빌드한다. 일부 패키지는 의도적이든(예: 무거운 빌드 종속성 사용, 주로 PyTorch) 아니든(예: 레거시 패키징 설정 사용) 빌드 격리와 호환되지 않는다.

특정 종속성에 대해 빌드 격리를 비활성화하려면 pyproject.toml 파일의 no-build-isolation-package 목록에 추가한다:

pyproject.toml
[project]
name = "project"
version = "0.1.0"
description = "..."
readme = "README.md"
requires-python = ">=3.12"
dependencies = ["cchardet"]

[tool.uv]
no-build-isolation-package = ["cchardet"]

빌드 격리 없이 패키지를 설치하려면 패키지 자체를 설치하기 전에 프로젝트 환경에 빌드 종속성을 먼저 설치해야 한다. 이는 빌드 종속성과 이를 필요로 하는 패키지를 별도의 extras로 분리하여 달성할 수 있다. 예를 들어:

pyproject.toml
[project]
name = "project"
version = "0.1.0"
description = "..."
readme = "README.md"
requires-python = ">=3.12"
dependencies = []

[project.optional-dependencies]
build = ["setuptools", "cython"]
compile = ["cchardet"]

[tool.uv]
no-build-isolation-package = ["cchardet"]

위와 같이 설정한 후, 사용자는 먼저 build 종속성을 동기화한다:

$ uv sync --extra build
 + cython==3.0.11
 + foo==0.1.0 (from file:///Users/crmarsh/workspace/uv/foo)
 + setuptools==73.0.1

그 다음 compile 종속성을 동기화한다:

$ uv sync --extra compile
 + cchardet==2.1.7
 - cython==3.0.11
 - setuptools==73.0.1

기본적으로 uv sync --extra compilecythonsetuptools 패키지를 제거한다. 빌드 종속성을 유지하려면 두 번째 uv sync 호출에 두 extras를 모두 포함한다:

$ uv sync --extra build
$ uv sync --extra build --extra compile

위의 cchardet와 같은 일부 패키지는 uv sync설치 단계에서만 빌드 종속성을 필요로 한다. 반면 flash-attn과 같은 패키지는 해결 단계에서 프로젝트의 lockfile을 해결하기 위해 빌드 종속성이 필요하다.

이런 경우, uv lock이나 uv sync 명령을 실행하기 전에 더 낮은 수준의 uv pip API를 사용해 빌드 종속성을 먼저 설치해야 한다. 예를 들어:

pyproject.toml
[project]
name = "project"
version = "0.1.0"
description = "..."
readme = "README.md"
requires-python = ">=3.12"
dependencies = ["flash-attn"]

[tool.uv]
no-build-isolation-package = ["flash-attn"]

다음 명령 시퀀스를 실행해 flash-attn을 동기화할 수 있다:

$ uv venv
$ uv pip install torch setuptools
$ uv sync

또는 dependency-metadata 설정을 통해 flash-attn 메타데이터를 미리 제공하여 종속성 해결 단계에서 패키지를 빌드할 필요를 없앨 수 있다. 예를 들어 flash-attn 메타데이터를 미리 제공하려면 pyproject.toml에 다음을 포함한다:

pyproject.toml
[[tool.uv.dependency-metadata]]
name = "flash-attn"
version = "2.6.3"
requires-dist = ["torch", "einops"]

Tip

flash-attn과 같은 패키지의 메타데이터를 확인하려면 해당 Git 저장소로 이동하거나 PyPI에서 패키지의 소스 배포판을 다운로드한다. 패키지 요구 사항은 일반적으로 setup.pysetup.cfg 파일에서 찾을 수 있다.

(패키지에 빌드된 배포판이 포함된 경우, 압축을 풀어 METADATA 파일을 찾을 수 있다. 그러나 빌드된 배포판이 있으면 메타데이터를 미리 제공할 필요가 없어지며, uv에서 이미 사용 가능하다.)

이렇게 포함한 후, 다시 두 단계의 uv sync 프로세스를 사용해 빌드 종속성을 설치할 수 있다. 다음 pyproject.toml이 주어지면:

pyproject.toml
[project]
name = "project"
version = "0.1.0"
description = "..."
readme = "README.md"
requires-python = ">=3.12"
dependencies = []

[project.optional-dependencies]
build = ["torch", "setuptools", "packaging"]
compile = ["flash-attn"]

[tool.uv]
no-build-isolation-package = ["flash-attn"]

[[tool.uv.dependency-metadata]]
name = "flash-attn"
version = "2.6.3"
requires-dist = ["torch", "einops"]

다음 명령 시퀀스를 실행해 flash-attn을 동기화할 수 있다:

$ uv sync --extra build
$ uv sync --extra build --extra compile

Note

tool.uv.dependency-metadataversion 필드는 레지스트리 기반 종속성의 경우 선택 사항이다(생략하면 uv는 메타데이터가 패키지의 모든 버전에 적용된다고 가정). 그러나 Git 종속성과 같은 직접 URL 종속성의 경우 필수 사항이다.

편집 가능 모드

기본적으로 프로젝트는 편집 가능 모드로 설치된다. 이 모드에서는 소스 코드를 변경하면 바로 환경에 반영된다. uv syncuv run 명령어는 모두 --no-editable 플래그를 지원한다. 이 플래그는 uv에게 프로젝트를 편집 불가능 모드로 설치하도록 지시한다. --no-editable 플래그는 Docker 컨테이너를 빌드하는 것과 같은 배포 시나리오를 위해 설계되었다. 이 경우 프로젝트는 원본 소스 코드에 의존하지 않고 배포 환경에 포함된다.

의존성 충돌 문제

uv는 프로젝트에서 선언된 모든 선택적 의존성(extras)이 서로 호환되어야 하며, lockfile을 생성할 때 모든 선택적 의존성을 함께 해결한다.

한 extra에 선언된 선택적 의존성이 다른 extra와 호환되지 않으면, uv는 프로젝트의 요구사항을 해결하지 못하고 오류를 발생시킨다.

이 문제를 해결하기 위해 uv는 충돌하는 extras를 선언하는 기능을 지원한다. 예를 들어, 서로 충돌하는 두 세트의 선택적 의존성이 있다고 가정해 보자:

pyproject.toml
[project.optional-dependencies]
extra1 = ["numpy==2.1.2"]
extra2 = ["numpy==2.0.0"]

위와 같은 의존성으로 uv lock을 실행하면 해결에 실패한다:

$ uv lock
  x No solution found when resolving dependencies:
  `-> Because myproject[extra2] depends on numpy==2.0.0 and myproject[extra1] depends on numpy==2.1.2, we can conclude that myproject[extra1] and
      myproject[extra2] are incompatible.
      And because your project requires myproject[extra1] and myproject[extra2], we can conclude that your projects's requirements are unsatisfiable.

하지만 extra1extra2가 충돌한다고 명시하면, uv는 이를 별도로 해결한다. 충돌 사항은 tool.uv 섹션에 명시한다:

pyproject.toml
[tool.uv]
conflicts = [
    [
      { extra = "extra1" },
      { extra = "extra2" },
    ],
]

이제 uv lock을 실행하면 성공한다. 단, 이제는 extra1extra2를 동시에 설치할 수 없다:

$ uv sync --extra extra1 --extra extra2
Resolved 3 packages in 14ms
error: extra `extra1`, extra `extra2` are incompatible with the declared conflicts: {`myproject[extra1]`, `myproject[extra2]`}

이 오류는 extra1extra2를 모두 설치하면 동일한 환경에 서로 다른 버전의 패키지가 설치되기 때문에 발생한다.

충돌하는 extras를 처리하는 위 전략은 의존성 그룹에도 적용된다:

pyproject.toml
[dependency-groups]
group1 = ["numpy==2.1.2"]
group2 = ["numpy==2.0.0"]

[tool.uv]
conflicts = [
    [
      { group = "group1" },
      { group = "group2" },
    ],
]

충돌하는 extras와의 유일한 차이점은 extra 대신 group을 사용해야 한다는 점이다.