PEP 735 – pyproject.toml의 의존성 그룹
- Author:
- Stephen Rosen <sirosen0 at gmail.com>
- Sponsor:
- Brett Cannon <brett at python.org>
- PEP-Delegate:
- Paul Moore <p.f.moore at gmail.com>
- Discussions-To:
- Discourse thread
- Status:
- Final
- Type:
- Standards Track
- Topic:
- Packaging
- Created:
- 20-Nov-2023
- Post-History:
- 14-Nov-2023, 20-Nov-2023
- Resolution:
- 10-Oct-2024
Table of Contents
번역·라이선스 안내
이 비공식 한국어 번역은 원문 Copyright 절의 Public Domain or CC0-1.0, whichever is more permissive 조건에 따라 제공합니다. 원저자와 공식 원문은 그대로 표시합니다. 수정되지 않은 기준 원문 · 공식 최신판
초록
이 PEP는 프로젝트의 빌드된 배포 패키지에 포함되지 않도록 pyproject.toml 파일에 패키지 요구 사항을 저장하는 메커니즘을 지정합니다.
이는 런처, IDE 및 기타 도구가 이름으로 찾고 식별할 수 있는 requirements.txt 파일과 유사한 명명된 의존성 그룹을 만드는 데 적합합니다.
여기에서 정의하는 기능을 “의존성 그룹”이라고 합니다.
동기
Python 커뮤니티에는 표준화된 답이 없는 주요 사용 사례가 두 가지 있습니다.
- 패키지의 개발 의존성은 어떻게 정의해야 합니까?
- 배포 패키지를 빌드하지 않는 프로젝트(비패키지 프로젝트)의 의존성은 어떻게 정의해야 합니까?
이러한 두 가지 요구 사항을 지원하기 위해 이 제안과 유사한 일반적인 해결책이 두 가지 있습니다.
requirements.txt파일- 패키지 extras
requirements.txt 파일과 extras에는 모두 이 표준이 극복하고자 하는 한계가 있습니다.
위의 두 사용 사례는 이 PEP가 지원하고자 하는 서로 다른 두 유형의 프로젝트를 설명한다는 점에 유의하십시오.
- 라이브러리와 같은 Python 패키지
- 데이터 과학 프로젝트와 같은 비패키지 프로젝트
동기를 부여하는 여러 사용 사례는 사용 사례 부록에 자세히 정의되어 있습니다.
requirements.txt 파일의 한계
많은 프로젝트에서는 하나 이상의 requirements.txt 파일을 정의할 수 있으며, 이를 프로젝트 루트(예: requirements.txt 및 test-requirements.txt)에 배치하거나 디렉터리(예: requirements/base.txt 및 requirements/test.txt)에 배치할 수 있습니다. 그러나 이러한 방식으로 요구 사항 파일을 사용하는 데에는 주요 문제가 있습니다.
- 도구가 이러한 파일을 이름으로 검색하거나 사용할 수 있도록 하는 표준화된 명명 규칙이 없습니다.
requirements.txt파일은 표준화되어 있지 않으며, 대신pip옵션을 제공합니다.
그 결과 requirements.txt 파일을 기반으로 도구의 동작을 정의하기가 어렵습니다. 이러한 파일은 이름으로 쉽게 검색하거나 식별할 수 없으며, 그 내용에는 패키지 지정자와 추가 pip 옵션이 섞여 있을 수 있습니다.
requirements.txt의 내용에 대한 표준이 없다는 것은 pip 이외의 도구로 처리하려는 어떤 대체 도구에도 이식할 수 없다는 의미이기도 합니다.
또한 requirements.txt 파일에서는 각 의존성 목록마다 파일이 하나씩 필요합니다. 일부 사용 사례에서는 이로 인해 의존성 그룹화의 한계 비용이 그 이점에 비해 높아집니다. 의존성이 여러 개의 작은 그룹으로 구성된 프로젝트에는 더 간결한 선언이 유용합니다.
이와 달리 의존성 그룹은 완전히 표준화된 내용을 사용하여 pyproject.toml 파일의 잘 알려진 위치에 정의됩니다. 의존성 그룹은 즉각적인 유용성을 제공할 뿐 아니라 향후 표준을 위한 출발점 역할도 합니다.
extras의 한계
extras는 [project.optional-dependencies] 테이블에 선언되는 추가 패키지 메타데이터입니다. 패키지 메타데이터의 일부로 게시되는 패키지 지정자 목록에 이름을 제공하며, 사용자는 해당 이름으로 요청할 수 있습니다. 예를 들어 pip install 'foo[bar]'처럼 foo를 bar 엑스트라와 함께 설치할 수 있습니다.
extras는 패키지 메타데이터이므로 정적으로 정의된다는 보장이 없으며, 이를 해결하려면 빌드 시스템이 필요할 수 있습니다. 또한 [project.optional-dependencies]의 정의는 많은 도구에 프로젝트가 패키지임을 나타내며, [project] 테이블의 유효성 검사와 같은 도구 동작을 유도할 수 있습니다.
패키지인 프로젝트에서 extras는 개발 의존성을 정의하는 일반적인 해결책이지만, 이러한 상황에서도 단점이 있습니다.
extra는 선택적 추가의존성을 정의하므로, 현재 패키지와 해당 의존성을 설치하지 않고는extra를 설치할 수 없습니다.- 사용자가 설치할 수 있으므로
extras는 패키지의 공개 인터페이스에 포함됩니다.extras는 게시되므로 패키지 개발자는 개발용 extras가 사용자 대상 extras와 혼동되지 않도록 하는 데 자주 주의를 기울입니다.
근거
이 PEP는 [dependency-groups] 테이블 내 목록에 요구 사항 데이터를 저장하는 방식을 정의합니다. 이 이름은 기능의 정식 이름(“Dependency Groups”)에 맞추기 위해 선택되었습니다.
이 형식은 가능한 한 단순하고 익히기 쉬워야 하며, 많은 경우 기존 requirements.txt 파일과 매우 유사한 형식을 갖추어야 합니다. [dependency-groups] 테이블의 각 목록은 패키지 지정자 목록으로 정의됩니다. 예를 들면 다음과 같습니다.
[dependency-groups]
test = ["pytest>7", "coverage"]
requirements.txt 파일에는 PEP 508 의존성 지정자로 표현할 수 없는 데이터를 요구하는 여러 사용 사례가 있습니다. 이러한 필드는 Dependency Groups에서 유효하지 않습니다. 색인 서버, 해시, 경로 의존성 등 pip가 지원하는 많은 데이터와 필드를 포함하려면 새로운 표준이 필요합니다. 이 표준은 새로운 표준과 발전을 위한 여지를 남겨 두지만, 유효한 requirements.txt 파일의 모든 내용을 지원하려고 하지는 않습니다.
이에 대한 유일한 예외는 requirements.txt 파일에서 한 파일을 다른 파일에 포함할 때 사용하는 -r 플래그입니다. Dependency Groups는 의미가 유사한 “include” 메커니즘을 지원하며, 이를 통해 한 의존성 그룹이 다른 의존성 그룹을 확장할 수 있습니다.
Dependency Groups에는 requirements.txt 파일과 유사한 두 가지 추가 기능이 있습니다.
- 빌드된 어떤 배포판에서도 별도의 메타데이터로 게시되지 않습니다.
- 의존성 그룹을 설치해도 패키지의 의존성이나 패키지 자체가 설치된다는 의미는 아닙니다.
사용 사례
다음 사용 사례는 이 PEP의 중요한 목표로 간주됩니다. 이들은 Use Cases Appendix에서 더 자세히 정의됩니다.
- 비 Python 패키징 빌드 프로세스를 통해 배포되는 웹 애플리케이션
- 게시되지 않은 개발 의존성 그룹이 있는 라이브러리
- 핵심 패키지 없이 의존성 그룹을 사용하는 데이터 과학 프로젝트
- 락파일 생성을 위한 입력 데이터 (Dependency Groups는 일반적으로 잠긴 의존성 데이터를 저장하는 위치로 사용해서는 안 됩니다)
- tox, Nox 또는 Hatch와 같은 환경 관리자를 위한 입력 데이터
- 테스트 및 린터 요구 사항을 구성 가능하게 탐색하는 IDE
Poetry 및 PDM 종속성 그룹에 관하여
기존 Poetry 및 PDM 도구는 각각 “Dependency Groups”라고 부르는 기능을 이미 제공합니다. 그러나 종속성 모음을 지정하는 표준이 없으므로, 각 도구는 [tool]테이블의 관련 섹션에서 이를 도구별 방식으로 정의합니다.
(PDM은 일부 종속성 그룹에 extras도 사용하며, 그 개념을 extras와 상당히 중복되게 다룹니다.)
이 PEP는 Poetry와 PDM의 모든 기능을 지원하지 않습니다. Poetry와 PDM은 pip용 requirements.txt 파일과 마찬가지로 일반 종속성 지정자에 대한 여러 비표준 확장을 지원합니다.
이러한 도구가 표준화된 종속성 그룹을 자체 종속성 그룹 메커니즘의 확장으로 사용할 수 있어야 합니다. 그러나 기존 Poetry 및 PDM 솔루션을 대체하는 새로운 데이터 형식을 정의하는 것은 목표가 아닙니다. 그렇게 하려면 이러한 도구에서 지원하는 경로 종속성과 같은 여러 추가 기능을 표준화해야 합니다.
종속성 그룹은 숨겨진 extras가 아닙니다.
종속성 그룹은 게시되지 않는 extras와 매우 유사합니다. 그러나 종속성 그룹과 extras를 추가로 구별하는 두 가지 주요 기능이 있습니다.
- 비패키지 프로젝트를 지원합니다.
- 종속성 그룹을 설치한다고 해서 패키지의 종속성 또는 패키지 자체가 설치되는 것은 아닙니다.
향후 호환성 및 잘못된 데이터
종속성 그룹은 향후 PEP에서 확장할 수 있도록 설계되었습니다. 그러나 종속성 그룹은 하나의 Python 프로젝트에서 여러 도구가 사용할 수 있어야 합니다. 여러 도구가 동일한 데이터를 사용하는 경우, 한 도구는 종속성 그룹을 확장하는 향후 PEP를 구현하지만 다른 도구는 구현하지 않을 수 있습니다.
이 경우 사용자를 지원하기 위해 이 PEP는 도구가 자신이 사용하는 종속성 그룹만 검사하도록 하는 유효성 검사 동작을 정의하고 권장합니다. 이를 통해 서로 다른 버전의 종속성 그룹 데이터를 사용하는 여러 도구가 pyproject.toml의 단일 테이블을 공유할 수 있습니다.
사양
이 PEP는 pyproject.toml 파일에 dependency-groups라는 새 섹션(테이블)을 정의합니다. dependency-groups테이블에는 사용자가 정의한 키가 임의의 개수만큼 포함되며, 각 키의 값은 요구 사항 목록(아래에 정의됨)입니다. 이러한 키는 유효한 비정규화 이름이어야 하며, 비교하기 전에 정규화되어야 합니다.
도구는 기본적으로 원래의 비정규화된 이름을 사용자에게 표시하는 것을 선호해야 합니다. 정규화 후 중복된 이름이 발견되면 도구는 오류를 발생시켜야 합니다.
dependency-groups아래의 요구 사항 목록에는 문자열, 테이블(파이썬에서 “dict”라고 하는 것) 또는 문자열과 테이블의 혼합이 포함될 수 있습니다.
요구 사항 목록의 문자열은 PEP 508에 정의된 유효한 Dependency Specifiers이어야 합니다.
요구 사항 목록의 테이블은 유효한 종속성 객체 지정자여야 합니다.
종속성 객체 지정자
종속성 객체 지정자는 종속성을 0개 이상 정의하는 테이블입니다.
이 PEP는 “의존성 그룹 포함”이라는 의존성 객체 지정자 유형 하나만 표준화합니다. 다른 유형은 향후 표준에서 추가될 수 있습니다.
의존성 그룹 포함
의존성 그룹 포함은 현재 의존성 그룹에 다른 의존성 그룹의 의존성을 포함합니다.
include는 정확히 하나의 키인 "include-group"를 갖는 표로 정의되며, 그 값은 다른 의존성 그룹의 이름인 문자열입니다.
예를 들어, {include-group = "test"}는 test의 의존성 그룹 내용을 포함하도록 확장되는 include입니다.
include는 이름이 지정된 의존성 그룹의 내용과 정확히 동등한 것으로 정의되며, include가 있는 위치에 현재 그룹으로 삽입됩니다. 예를 들어, foo = ["a", "b"]가 한 그룹이고 bar = ["c", {include-group = "foo"}, "d"]가 다른 그룹이라면, 의존성 그룹 포함이 확장될 때 bar는 ["c", "a", "b", "d"]로 평가되어야 합니다.
의존성 그룹 포함은 동일한 패키지를 여러 번 지정할 수 있습니다. 도구는 include로 생성된 목록 내용을 중복 제거하거나 그 밖의 방식으로 변경해서는 안 됩니다. 예를 들어, 다음 표가 주어졌을 때:
[dependency-groups]
group-a = ["foo"]
group-b = ["foo>1.0"]
group-c = ["foo<1.0"]
all = ["foo", {include-group = "group-a"}, {include-group = "group-b"}, {include-group = "group-c"}]
all의 해석된 값은 ["foo", "foo", "foo>1.0", "foo<1.0"]이어야 합니다. 도구는 서로 다른 버전 제약 조건으로 동일한 요구 사항을 여러 번 처리하도록 요청받는 다른 모든 경우와 정확히 동일하게 이러한 목록을 처리해야 합니다.
의존성 그룹 포함은 의존성 그룹 포함을 담은 목록을 포함할 수 있으며, 이 경우 해당 include도 확장되어야 합니다. 의존성 그룹 포함에는 순환이 포함되어서는 안 되며, 도구는 순환을 감지하면 오류를 보고해야 합니다.
의존성 그룹 표 예시
다음은 이를 사용하여 네 개의 의존성 그룹인 test, docs, typing, typing-test를 정의하는 일부 pyproject.toml의 예시입니다.
[dependency-groups]
test = ["pytest", "coverage"]
docs = ["sphinx", "sphinx-rtd-theme"]
typing = ["mypy", "types-requests"]
typing-test = [{include-group = "typing"}, {include-group = "test"}, "useful-types"]
이러한 의존성 그룹 선언 중 어느 것도 현재 패키지, 해당 의존성 또는 선택적 의존성을 암시적으로 설치하지 않는다는 점에 유의하십시오. 패키지를 테스트할 때 test와 같은 의존성 그룹을 사용하려면 사용자의 구성 또는 도구 모음에서도 현재 패키지(.)를 설치해야 합니다. 예를 들면,
$TOOL install-dependency-group test
pip install -e .
의존성 그룹 설치를 지원하는 도구가 $TOOL이라고 가정할 때, 테스트 환경을 구축하는 데 사용할 수 있습니다.
이를 통해 프로젝트를 패키지로 설치하지 않고도 docs 의존성 그룹을 사용할 수 있습니다.
$TOOL install-dependency-group docs
패키지 빌드
빌드 백엔드는 빌드된 배포 패키지에 패키지 메타데이터로 의존성 그룹 데이터를 포함해서는 안 됩니다. 즉, sdist의 PKG-INFO와 wheel의 METADATA에는 의존성 그룹을 포함하는 참조 가능한 필드가 들어 있지 않습니다.
동적 메타데이터를 평가할 때 의존성 그룹을 사용하는 것은 유효하며, sdist에 포함된 pyproject.toml 파일에는 자연스럽게 [dependency-groups]표가 계속 포함됩니다. 그러나 표의 내용은 게시된 패키지 인터페이스의 일부가 아닙니다.
의존성 그룹 설치
의존성 그룹을 지원하는 도구는 사용자가 의존성 그룹에서 설치할 수 있도록 새로운 옵션과 인터페이스를 제공해야 합니다.
패키지의 의존성 그룹을 표현하는 구문은 다음 두 가지 이유로 정의되지 않습니다.
- 데이터가 게시되지 않는 것으로 정의되어 있으므로 PyPI에서 서드파티 패키지의 의존성 그룹을 참조하는 것은 유효하지 않습니다.
- 의존성 그룹에 현재 패키지가 존재한다는 보장이 없으며, 의존성 그룹의 목적 중 하나는 패키지가 아닌 프로젝트를 지원하는 것이기 때문입니다.
예를 들어, 의존성 그룹을 설치하기 위한 가능한 pip 인터페이스는 다음과 같습니다:
pip install --dependency-groups=test,typing
이는 단지 예시임을 유의하십시오. 이 PEP는 도구가 의존성 그룹의 설치를 지원하는 방식에 관한 어떠한 요구 사항도 명시하지 않습니다.
Extras와 겹치는 설치 UX
도구는 Extras를 설치할 때 제공하는 것과 동일한 인터페이스를 의존성 그룹 설치에도 제공하도록 선택할 수 있습니다.
이 사양은 이름이 의존성 그룹과 일치하는 Extra를 갖는 것을 금지하지 않음을 유의하십시오.
사용자는 이름이 Extras와 일치하는 의존성 그룹을 만들지 않는 것이 좋습니다. 도구는 이러한 일치를 오류로 처리할 수 있습니다.
검증 및 하위 호환성
의존성 그룹을 지원하는 도구는 사용하기 전에 데이터를 검증하려 할 수 있습니다. 그러나 이러한 검증 동작을 구현하는 도구는 이 사양의 향후 확장을 허용하도록 주의해야 하며, 새로운 구문이 존재할 때 불필요하게 오류나 경고를 내보내지 않아야 합니다.
도구는 의존성 그룹에서 인식할 수 없는 데이터를 평가하거나 처리할 때 오류를 발생시켜야 합니다.
도구는 모든 의존성 그룹의 목록 내용을 선제적으로 검증해서는 안 됩니다.
이는 다음 데이터가 있을 때 대부분의 도구가 foo그룹의 사용은 허용하고, bar그룹을 사용할 때만 오류를 발생시킨다는 의미입니다.
[dependency-groups]
foo = ["pyparsing"]
bar = [{set-phasers-to = "stun"}]
린터와 검증 도구는 더 엄격할 수 있습니다.
주로 의존성 그룹을 설치하거나 해결하는 도구에서는 선제적 검증을 권장하지 않습니다. 린터와 검증 도구에는 이 권고를 따르지 않을 충분한 이유가 있을 수 있습니다.
참조 구현
다음 참조 구현은 의존성 그룹의 내용을 줄바꿈으로 구분하여 stdout에 출력합니다. 따라서 출력은 유효한 requirements.txt 데이터입니다.
import re
import sys
import tomllib
from collections import defaultdict
from packaging.requirements import Requirement
def _normalize_name(name: str) -> str:
return re.sub(r"[-_.]+", "-", name).lower()
def _normalize_group_names(dependency_groups: dict) -> dict:
original_names = defaultdict(list)
normalized_groups = {}
for group_name, value in dependency_groups.items():
normed_group_name = _normalize_name(group_name)
original_names[normed_group_name].append(group_name)
normalized_groups[normed_group_name] = value
errors = []
for normed_name, names in original_names.items():
if len(names) > 1:
errors.append(f"{normed_name} ({', '.join(names)})")
if errors:
raise ValueError(f"Duplicate dependency group names: {', '.join(errors)}")
return normalized_groups
def _resolve_dependency_group(
dependency_groups: dict, group: str, past_groups: tuple[str, ...] = ()
) -> list[str]:
if group in past_groups:
raise ValueError(f"Cyclic dependency group include: {group} -> {past_groups}")
if group not in dependency_groups:
raise LookupError(f"Dependency group '{group}' not found")
raw_group = dependency_groups[group]
if not isinstance(raw_group, list):
raise ValueError(f"Dependency group '{group}' is not a list")
realized_group = []
for item in raw_group:
if isinstance(item, str):
# packaging.requirements.Requirement parsing ensures that this is a valid
# PEP 508 Dependency Specifier
# raises InvalidRequirement on failure
Requirement(item)
realized_group.append(item)
elif isinstance(item, dict):
if tuple(item.keys()) != ("include-group",):
raise ValueError(f"Invalid dependency group item: {item}")
include_group = _normalize_name(next(iter(item.values())))
realized_group.extend(
_resolve_dependency_group(
dependency_groups, include_group, past_groups + (group,)
)
)
else:
raise ValueError(f"Invalid dependency group item: {item}")
return realized_group
def resolve(dependency_groups: dict, group: str) -> list[str]:
if not isinstance(dependency_groups, dict):
raise TypeError("Dependency Groups table is not a dict")
if not isinstance(group, str):
raise TypeError("Dependency group name is not a str")
return _resolve_dependency_group(dependency_groups, group)
if __name__ == "__main__":
with open("pyproject.toml", "rb") as fp:
pyproject = tomllib.load(fp)
dependency_groups_raw = pyproject["dependency-groups"]
dependency_groups = _normalize_group_names(dependency_groups_raw)
print("\n".join(resolve(pyproject["dependency-groups"], sys.argv[1])))
하위 호환성
이 글을 작성하는 시점에는 pyproject.toml 파일 내의 dependency-groups 네임스페이스가 사용되지 않습니다. 최상위 네임스페이스는 packaging.python.org에 명시된 표준에서만 사용하도록 예약되어 있으므로, 직접적인 하위 호환성 문제는 없습니다.
그러나 이 기능의 도입은 여러 생태계 도구에 영향을 미치며, 특히 setup.py 및 requirements.txt의 데이터 검토를 지원하려는 도구에 영향을 줍니다.
감사 및 업데이트 도구
매우 다양한 도구가 requirements.txt 파일에 표현된 Python 의존성 데이터를 이해합니다. (예: Dependabot, Tidelift 등)
이러한 도구는 의존성 데이터를 검사하며, 경우에 따라 도구 지원 또는 완전 자동화된 업데이트를 제공합니다. 처음에는 이러한 도구 중 어느 것도 새로운 의존성 그룹을 지원하지 않을 것이며, 광범위한 생태계 지원이 이루어지기까지 수개월 또는 몇 년이 걸릴 수도 있다고 예상합니다.
그 결과 의존성 그룹 사용자는 의존성 그룹을 사용하기 시작하는 시점에 작업 흐름과 도구 지원의 저하를 경험하게 됩니다. 이는 의존성 데이터가 저장되는 위치와 방식에 관한 모든 새로운 표준에 해당합니다.
보안 관련 영향
이 PEP는 프로젝트에서 의존성 정보를 지정하기 위한 새로운 구문과 데이터 형식을 도입합니다. 그러나 의존성을 처리하거나 해결하기 위해 새롭게 지정된 메커니즘을 도입하지는 않습니다.
따라서 의존성을 설치하는 데 이미 사용될 수 있는 모든 도구에 내재한 보안 우려 외에는 보안 우려를 수반하지 않습니다. 즉, requirements.txt 파일에 지정할 수 있는 것과 마찬가지로 이곳에도 악성 의존성을 지정할 수 있습니다.
이를 가르치는 방법
이 기능은 정식 명칭인 “Dependency Groups”로 지칭해야 합니다.
기본적인 사용 형태는 일반적인 requirements.txt 데이터의 변형으로 가르쳐야 합니다. 표준 의존성 지정자(PEP 508)를 이름이 지정된 목록에 추가할 수 있습니다. pip에 requirements.txt 파일에서 설치하도록 요청하는 대신, pip 또는 관련 워크플로 도구가 이름이 지정된 의존성 그룹에서 설치합니다.
새로운 Python 사용자의 경우, 현재 requirements.txt 파일을 사용하도록 가르치는 것과 마찬가지로 의존성 그룹이 포함된 섹션을 pyproject.toml에 직접 만들도록 가르칠 수 있습니다. 이를 통해 새로운 Python 사용자는 패키지 빌드를 배울 필요 없이 pyproject.toml 파일에 대해서도 배울 수 있습니다. [dependency-groups]만 포함하고 다른 테이블은 포함하지 않는 pyproject.toml 파일도 유효합니다.
신규 사용자와 숙련된 사용자 모두에게 의존성 그룹 포함 기능을 설명해야 합니다. requirements.txt 사용 경험이 있는 사용자에게는 이를 -r의 유사 기능으로 설명할 수 있습니다. 신규 사용자에게는 포함을 통해 한 의존성 그룹이 다른 의존성 그룹을 확장할 수 있다고 가르쳐야 합니다. 유사한 구성 인터페이스와 Python의 list.extend 메서드를 비유로 사용하여 이 개념을 설명할 수 있습니다.
setup.py 패키징을 사용해 본 Python 사용자는 pyproject.toml보다 앞서 사용되었으며 패키지 메타데이터가 동적으로 정의되는 일반적인 관행에 익숙할 수 있습니다. requirements.txt 파일에서 불러온 요구 사항과 setup() 호출 전에 정의된 정적 목록은 의존성 그룹과 쉽게 유사하게 설명할 수 있습니다.
의존성 그룹 사용을 위한 인터페이스
이 명세는 project 테이블을 통해 빌드된 패키지에 포함하는 것 외에는 의존성 그룹과 상호 작용하기 위한 보편적인 인터페이스를 제공하지 않습니다. 이는 도구 작성자와 사용자 모두에게 영향을 미칩니다.
도구 작성자는 의존성 그룹이 사용자 사례와 관련이 있는지, 그리고 어느 정도 관련이 있는지를 판단하고, 이에 맞는 자체 인터페이스를 구축해야 합니다. 환경 관리자, 해석기, 설치 관리자 및 관련 비빌드 도구의 경우 “PEP 735 Dependency Groups”를 지원한다고 문서화할 수 있지만, 사용 방식을 문서화할 책임은 각 도구에 있습니다. 빌드 백엔드가 의존성 그룹을 지원하려면 project 테이블에서의 포함을 지원해야 하지만, 그 밖의 엄격한 요구 사항은 설정하지 않습니다.
사용자에게 가장 중요한 결과는 패키지 빌드 외부에서 의존성 그룹을 사용하려 할 때마다 관련 도구의 문서를 확인해야 한다는 점입니다. 도구는 문서, 런타임 경고 또는 오류를 통해 권장되지 않거나 지원되지 않는 사용 방식에 대해 사용자에게 알려야 합니다. 예를 들어 도구가 모든 의존성 그룹이 상호 호환되며 서로 모순되는 패키지 지정자를 포함하지 않도록 요구하려는 경우, 해당 제한을 문서화하고 사용 목적에 맞게 의존성 그룹을 활용하는 방법을 사용자에게 안내해야 합니다.
거부된 아이디어
각 의존성 그룹을 테이블로 정의하지 않는 이유는 무엇입니까?
향후 확장을 허용하는 것이 목표라면, 각 그룹에 향후 키를 추가할 수 있도록 각 의존성 그룹을 하위 테이블로 정의하는 방식이 가장 큰 향후 유연성을 제공합니다.
그러나 이렇게 하면 구조가 더 깊게 중첩되므로 가르치고 배우기가 더 어려워집니다. 이 PEP의 목표 중 하나는 다양한 requirements.txt 사용 사례를 쉽게 대체하는 것입니다.
의존성 지정자를 확장하기 위한 특수 문자열 구문을 정의하지 않는 이유는 무엇입니까?
이 명세의 초기 초안에서는 의존성 그룹 포함과 경로 의존성을 위한 구문 형식을 정의했습니다.
그러나 이 접근 방식에는 세 가지 주요 문제가 있었습니다:
- PEP 508에 더해 가르쳐야 하는 문자열 구문을 복잡하게 만듭니다.
- 결과 문자열은 항상 PEP 508 지정자와 구별해야 하므로 구현이 복잡해집니다.
PEP 508이 아닌 의존성 지정자를 더 허용하면 어떻습니까?
논의 중에 PEP 508으로 가능한 것보다 더 표현력 있는 지정자가 필요한 여러 사용 사례가 등장했습니다.
로컬 경로를 가리키는 “Path Dependencies”와 [project.dependencies]에 대한 참조가 특히 큰 관심을 끌었습니다.
그러나 이러한 기능에 대한 기존 표준은 없습니다(pip의 구현 세부 사항이라는 사실상의 표준은 제외합니다).
결과적으로 이러한 기능과 pip의 동작을 표준화하려고 하면, 이 PEP에 해당 기능을 포함하는 작업의 범위가 크게 확장됩니다.
pip install -e와 PEP 660으로 표현되는 편집 가능 설치의 표현을 표준화하려는 시도에 특별한 관심을 기울였습니다. 그러나 빌드 백엔드에서는 편집 가능 설치의 생성이 표준화되어 있지만, 설치 도구에서는 편집 가능 설치의 동작이 표준화되어 있지 않습니다. 이 PEP에 편집 가능 설치를 포함하려면 이를 지원하는 모든 도구가 편집 가능 설치를 설치할 수 있어야 합니다.
따라서 Poetry와 PDM이 이러한 기능 중 일부에 대한 구문을 제공하지만, 현재로서는 의존성 그룹에 포함하기에 충분히 표준화되지 않은 것으로 간주됩니다.
테이블의 이름을 [run], [project.dependency-groups] 등으로 지정하지 않은 이유는 무엇입니까?
이 개념에는 가능한 이름이 많습니다. 이 테이블은 이미 존재하는 [project.dependencies] 및 [project.optional-dependencies] 테이블과 함께 존재해야 하며, 새로운 [external] 의존성 테이블과도 함께 존재할 수 있습니다(현재 작성 시점에는 [external] 테이블을 정의하는 PEP 725이 진행 중입니다).
[run]은 초기 논의에서 유력한 제안이었지만, 제안된 사용 방식은 단일 런타임 의존성 집합을 중심으로 했습니다. 이 PEP는 여러 의존성 그룹을 명시적으로 설명하므로 [run]은 적합성이 떨어집니다. 이는 특정 런타임 컨텍스트를 위한 의존성 데이터만이 아니라 여러 컨텍스트를 위한 데이터입니다.
[project.dependency-groups]는 [project.dependencies] 및 [project.optional-dependencies]와 좋은 병행 관계를 제공하지만, 비패키지 프로젝트에는 주요 단점이 있습니다. [project]는 name 및 version과 같은 여러 키를 정의해야 합니다. 이 이름을 사용하면 이러한 키가 없어도 되도록 [project] 테이블을 재정의해야 하거나, 비패키지 프로젝트가 이러한 키를 정의하고 사용하도록 요구하게 됩니다. 더 나아가 이는 사실상 모든 비패키지 프로젝트가 스스로 패키지로 취급될 수 있도록 해야 한다는 요구 사항이 됩니다.
pip가 계획한 --only-deps 구현으로 충분하지 않은 이유는 무엇입니까?
pip는 현재 –only-deps flag를 추가하는 기능을 로드맵에 포함하고 있습니다. 이 플래그는 사용자가 현재 패키지를 설치하지 않고 패키지 의존성과 엑스트라를 설치할 수 있도록 하기 위한 것입니다.
이는 비패키지 프로젝트의 요구 사항을 해결하지 못하며, 패키지 의존성 없이 엑스트라를 설치할 수도 없습니다.
<environment manager>가 해결책이 아닌 이유는 무엇입니까?
tox, Nox, Hatch와 같은 기존 환경 관리자는 이미 구성 데이터의 일부로 인라인 의존성을 나열할 수 있습니다. 이는 많은 개발 의존성 요구 사항을 충족하며, 실행할 수 있는 관련 작업과 의존성 그룹을 명확히 연결합니다. 이러한 메커니즘은 good하지만 sufficient하지 않습니다.
첫째, 비패키지 프로젝트의 요구 사항을 해결하지 못합니다.
둘째, 다른 도구가 이 데이터에 액세스하는 데 사용할 표준이 없습니다. 이는 IDE와 Dependabot 같은 고수준 도구에 영향을 미치며, 이러한 의존성 그룹과의 긴밀한 통합을 지원할 수 없게 합니다. (예를 들어, 현재 작성 시점에 Dependabot은 tox.ini 파일에 고정된 의존성을 표시하지 않습니다.)
보류된 아이디어
[project.dependencies] 또는 [project.optional-dependencies]에서 의존성 그룹 포함을 지원하지 않는 이유는 무엇입니까?
이 사양의 초기 초안에서는 [project]테이블에서 종속성 그룹 포함을 사용할 수 있도록 허용했습니다. 그러나 커뮤니티 피드백 과정에서 여러 문제가 제기되었으며, 이로 인해 해당 기능이 제거되었습니다.
종속성 그룹을 포함해도 해결되는 추가 사용 사례는 소수에 불과했으며, 사양의 범위가 크게 확대되었습니다. 특히 이 포함 기능으로 인해 추가의 영향을 받는 당사자의 수가 증가합니다. 빌드 백엔드, SBOM 생성기, 종속성 분석기를 비롯한 [project]테이블의 많은 소비자는 [project]의 변경으로 영향을 받지만, 새롭고 서로 연결되지 않은 [dependency-groups]테이블이 추가되어도 기존과 같이 계속 작동할 수 있습니다.
위의 우려와는 별개로, [project]테이블에서 종속성 그룹을 포함할 수 있도록 허용하면 패키지 유지 관리자가 종속성 메타데이터를 현재의 표준 위치에서 옮기도록 장려하게 됩니다. 이는 정적 pyproject.toml메타데이터를 복잡하게 만들며, 종속성 메타데이터를 하나의 위치에 저장하려는 PEP 621의 목표와 충돌합니다.
마지막으로, 이 PEP에서 [project]지원을 제외하는 것은 최종 결정이 아닙니다. 해당 테이블에서 포함을 사용하는 방식이나 [dependency-groups]에서 [project]로 포함하는 구문은 향후 PEP에서 도입되어 그 자체의 장점에 따라 검토될 수 있습니다.
[project]에서 종속성 그룹 포함을 사용하는 사례
이 PEP에서는 보류되었지만, [project]테이블에서 포함을 허용하면 여러 사용 사례를 해결할 수 있습니다.
특히 패키지 개발자가 패키지 자체는 제외하고 패키지의 종속성만 설치하려는 경우가 있습니다.
예를 들면 다음과 같습니다.
- 종속성을 빌드할 때와 패키지 자체를 빌드할 때 서로 다른 환경 변수 또는 옵션 지정
- 설치 중인 패키지와 종속성을 서로 격리하는 계층형 컨테이너 이미지 생성
- 패키지 자체를 빌드하고 설치하지 않고도 분석 환경(예: 타입 검사)에 종속성 제공
마지막 사례의 예로 다음 샘플 pyproject.toml을 고려하십시오.
[project]
dependencies = [{include = "runtime"}]
[optional-dependencies]
foo = [{include = "foo"}]
[dependency-groups]
runtime = ["a", "b"]
foo = ["c", "d"]
typing = ["mypy", {include = "runtime"}, {include = "foo"}]
이 경우 모든 패키지의 런타임 종속성을 포함하되 패키지 자체는 포함하지 않는 typing그룹을 정의할 수 있습니다. 이를 통해 typing종속성 그룹을 사용할 때 패키지 설치를 건너뛸 수 있습니다. 이는 더 효율적일 뿐만 아니라 테스트 시스템에 필요한 요구 사항을 줄일 수도 있습니다.
[build-system.requires]에서 종속성 그룹 포함을 지원하지 않는 이유는 무엇입니까?
[project]에서 종속성 그룹을 사용하는 것을 허용하지 않을 것이므로, [build-system.requires]는 [project.dependencies]와 비교하여 검토할 수 있습니다.
그룹에 지정된 빌드 요구 사항은 패키지 요구 사항보다 이론적으로 사용되는 경우가 적습니다. 또한 이러한 변경의 영향은 PEP 517 프런트엔드에도 미치며, 빌드 환경을 준비하려면 해당 프런트엔드가 종속성 그룹을 지원해야 합니다.
[project.dependencies]및 [project.optional-dependencies]의 변경과 비교하면 [build-system.requires]의 동작을 변경하는 것은 영향이 더 크고 잠재적인 사용 사례는 더 적습니다. 따라서 이 PEP가 [project]테이블을 변경하지 않기로 했으므로 [build-system]을 변경하는 것도 보류됩니다.
현재 프로젝트를 포함하는 종속성 그룹을 지원하지 않는 이유는 무엇입니까?
종속성 그룹의 여러 사용 시나리오는 [project]테이블에 정의된 패키지와 함께 종속성 그룹을 설치하는 것과 관련됩니다. 예를 들어 패키지를 테스트하려면 테스트 종속성과 패키지 자체를 설치해야 합니다. 또한 종속성 그룹과 주 패키지 간의 호환성은 잠금 파일 생성기에 유용한 입력 정보입니다.
이러한 경우 종속성 그룹이 프로젝트 자체에 의존한다고 선언할 수 있으면 바람직합니다. 논의에서 제시된 예시 구문에는 {include-project = true} 및 {include-group = ":project:"}가 포함되었습니다.
그러나 Path Dependencies로 PEP 508을 확장하는 사양이 수립되면, Dependency Groups에서 주 패키지를 지정하는 방법이 두 가지가 됩니다. 예를 들어 .이 공식적으로 지원되고 이 PEP에 {include-project = true}가 포함되면, dependency groups는 다음 그룹 중 하나를 지정할 수 있습니다.
[dependency-groups]
case1 = [{include-project = true}]
case2 = ["."]
case3 = [{include-project = true}, "."]
case4 = [{include-project = false}, "."]
여러 가지 옵션이 pyproject.toml에 정의된 패키지를 지정하는 혼란스러운 미래를 피하기 위해, 이 관계를 선언하는 구문은 이 PEP에서 생략합니다.
부록 A: 비 Python 언어의 선행 사례
이 절은 주로 정보 제공을 목적으로 하며, 다른 언어 생태계가 유사한 문제를 해결하는 방법을 문서화합니다.
JavaScript와 package.json
JavaScript 커뮤니티에서 패키지는 pyproject.toml과 범위가 유사한 표준 구성 및 데이터 파일을 package.json에 포함합니다.
package.json의 두 키인 "dependencies"와 "devDependencies"가 의존성 데이터를 제어합니다. "dependencies"의 역할은 사실상 pyproject.toml의 [project.dependencies]와 동일하며, 패키지의 직접 의존성을 선언합니다.
"dependencies"데이터
의존성 데이터는 package.json에서 패키지 이름과 버전 지정자의 매핑으로 선언합니다.
버전 지정자는 Python의 PEP 440 버전 지정자와 유사하게 가능한 버전, 범위 및 기타 값에 대한 간단한 문법을 지원합니다.
예를 들어 다음은 몇 가지 의존성을 선언하는 package.json의 일부입니다.
{
"dependencies": {
"@angular/compiler": "^17.0.2",
"camelcase": "8.0.0",
"diff": ">=5.1.0 <6.0.0"
}
}
@ 기호는 조직이 소유한 패키지의 패키지 소유자를 선언하는 scope입니다. 따라서 "@angular/compiler"는 angular 소유권 아래 그룹화된 compiler라는 패키지를 선언합니다.
URL 및 로컬 경로를 참조하는 의존성
의존성 지정자는 Python 패키징의 규정과 유사하게 URL 및 Git 저장소를 위한 구문을 지원합니다.
URL은 버전 번호 대신 사용할 수 있습니다. URL을 사용하면 이는 암묵적으로 패키지 소스 코드의 tarball을 가리킵니다.
Git 저장소도 이와 유사하게 사용할 수 있으며, committish 지정자도 지원합니다.
NPM에서는 PEP 440와 달리 의존성을 위한 패키지 소스 코드 디렉터리에 로컬 경로를 사용할 수 있습니다. 표준 npm install --save명령을 통해 이 데이터를 package.json에 추가하면, 해당 경로는 package.json을 포함하는 디렉터리를 기준으로 하는 상대 경로로 정규화되고 file:접두사가 붙습니다. 예를 들어 다음의 일부 package.json에는 현재 디렉터리의 형제 디렉터리에 대한 참조가 포함되어 있습니다.
{
"dependencies": {
"my-package": "file:../foo"
}
}
official NPM documentation에 따르면 로컬 경로 의존성은 공개 패키지 저장소에 게시되어서는 안 되지만, 게시된 패키지에 포함된 이러한 의존성 데이터 자체의 유효성 또는 무효성에 대해서는 언급하지 않습니다.
"devDependencies"데이터
package.json에는 "dependencies"와 동일한 형식으로 "devDependencies"라는 두 번째 섹션을 포함할 수 있습니다. "devDependencies"에 선언된 의존성은 패키지 저장소에서 패키지를 설치할 때(예: 의존성 해결의 일부로) 기본적으로 설치되지 않지만, package.json을 포함하는 소스 트리에서 npm install이 실행되면 설치됩니다.
"dependencies"가 URL과 로컬 경로를 지원하는 것처럼 "devDependencies"도 이를 지원합니다.
"peerDependencies"와 "optionalDependencies"
package.json에는 관련성이 있는 두 가지 추가 섹션이 있습니다.
"peerDependencies"는 "dependencies"와 동일한 형식으로 의존성 목록을 선언하지만, 이 목록이 호환성 선언이라는 의미를 가집니다. 예를 들어 다음 데이터는 버전 2의 foo패키지와의 호환성을 선언합니다:
{
"peerDependencies": {
"foo": "2.x"
}
}
"optionalDependencies"는 가능한 경우 설치해야 하지만 사용할 수 없더라도 실패로 취급해서는 안 되는 의존성 목록을 선언합니다. 또한 "dependencies"와 동일한 매핑 형식을 사용합니다.
"peerDependenciesMeta"
"peerDependenciesMeta"는 "peerDependencies"를 처리하는 방법을 추가로 제어할 수 있는 섹션입니다.
다음 예시와 같이 이 섹션에서 패키지를 optional로 설정하면 누락된 의존성에 대한 경고를 비활성화할 수 있습니다.
{
"peerDependencies": {
"foo": "2.x"
},
"peerDependenciesMeta": {
"foo": {
"optional": true
}
}
}
--omit 및 --include
npm install 명령은 --omit 및 --include라는 두 가지 옵션을 지원하며, 이를 통해 “prod”, “dev”, “optional” 또는 “peer” 의존성을 설치할지 제어할 수 있습니다.
“prod”라는 이름은 "dependencies"아래에 나열된 의존성을 가리킵니다.
기본적으로 소스 트리에서 npm install을 실행하면 네 그룹이 모두 설치되지만, 이 옵션을 사용하면 설치 동작을 더 정밀하게 제어할 수 있습니다. 또한 이러한 값을 .npmrc 파일에 선언할 수 있으므로 사용자별 및 프로젝트별 구성을 통해 설치 동작을 제어할 수 있습니다.
Ruby 및 Ruby Gems
Ruby 프로젝트는 Ruby 생태계에서 패키지(“gem”)를 생성하도록 의도되었을 수도 있고 그렇지 않을 수도 있습니다. 실제로 대부분의 언어 사용자는 gem을 생성하고 싶어 하지 않으며, 자체 패키지를 만드는 데 관심도 없을 것으로 예상됩니다. 많은 튜토리얼에서는 패키지를 생성하는 방법을 다루지 않으며, 도구 체인도 지원되는 사용 사례를 위해 사용자 코드를 패키징하도록 요구하지 않습니다.
Ruby는 요구 사항 지정을 두 개의 별도 파일로 나눕니다.
Gemfile: 의존성 그룹 형태의 요구 사항 데이터만 지원하는 전용 파일<package>.gemspec: 패키지(gem) 메타데이터를 선언하는 전용 파일
bundler 도구는 bundle 명령을 제공하며, Gemfile데이터를 사용하기 위한 기본 인터페이스입니다.
gem 도구는 gem build 명령을 통해 .gemspec 데이터에서 gem을 빌드합니다.
Gemfile 및 bundle
Gemfile은 여러 group 선언으로 둘러싸인 gem 지시어를 포함하는 Ruby 파일입니다. gem 지시어는 group 선언 외부에서도 사용할 수 있으며, 이 경우 암시적으로 이름이 지정되지 않은 의존성 그룹을 구성합니다.
예를 들어 다음 Gemfile은 rails를 프로젝트 의존성으로 나열합니다. 그 밖의 모든 의존성은 그룹 아래에 나열됩니다.
source 'https://rubygems.org'
gem 'rails'
group :test do
gem 'rspec'
end
group :lint do
gem 'rubocop'
end
group :docs do
gem 'kramdown'
gem 'nokogiri'
end
사용자가 이 데이터로 bundle install을 실행하면 모든 그룹이 설치됩니다. 사용자는 수동으로 또는 CLI를 통해 .bundle/config에서 bundler 구성을 생성하거나 수정하여 그룹을 선택 해제할 수 있습니다. 예를 들어 bundle config set --local without 'lint:docs'를 사용할 수 있습니다.
위 데이터로는 최상위 수준에서 'rails'gem을 사용하는 것을 제외하거나 해당 암시적 그룹을 이름으로 참조할 수 없습니다.
gemspec 및 패키징된 의존성 데이터
gemspec file은 Gem::Specification 인스턴스 선언을 포함하는 Ruby 파일입니다.
Gem::Specification에서 패키지 의존성 데이터와 관련된 필드는 두 개뿐입니다. 이는 add_development_dependency 및 add_runtime_dependency입니다. Gem::Specification 객체는 의존성을 동적으로 추가하는 메서드도 제공하며, 여기에는 런타임 의존성을 추가하는 add_dependency가 포함됩니다.
다음은 단순화하기 위해 많은 필드를 제거하거나 줄인 rails.gemspec 파일의 변형입니다:
version = '7.1.2'
Gem::Specification.new do |s|
s.platform = Gem::Platform::RUBY
s.name = "rails"
s.version = version
s.summary = "Full-stack web application framework."
s.license = "MIT"
s.author = "David Heinemeier Hansson"
s.files = ["README.md", "MIT-LICENSE"]
# shortened from the real 'rails' project
s.add_dependency "activesupport", version
s.add_dependency "activerecord", version
s.add_dependency "actionmailer", version
s.add_dependency "activestorage", version
s.add_dependency "railties", version
end
add_development_dependency를 사용하지 않는다는 점에 유의하십시오. 일부 다른 주류 주요 패키지(예: rubocop)는 젬에서 개발 의존성을 사용하지 않습니다.
다른 프로젝트는 이 기능을 사용하기도 합니다. 예를 들어, kramdown은 개발 의존성을 사용하며, 해당 Rakefile에 다음과 같은 명세가 포함되어 있습니다:
s.add_dependency "rexml"
s.add_development_dependency 'minitest', '~> 5.0'
s.add_development_dependency 'rouge', '~> 3.0', '>= 3.26.0'
s.add_development_dependency 'stringex', '~> 1.5.1'
개발 의존성의 목적은 .gemspec의 일부로 암시적 그룹을 선언하는 것뿐이며, 이후 bundler에서 이를 사용할 수 있습니다.
자세한 내용은 bundler의 Gemfile에 관한 문서에 있는 gemspec 지시어를 참조하십시오. 그러나 .gemspec 개발 의존성과 Gemfile/bundle 사용 간의 통합은 예제를 통해 이해하는 것이 가장 좋습니다.
gemspec 개발 의존성 예제
다음과 같이 간단한 프로젝트가 Gemfile과 .gemspec 형식으로 있다고 가정하십시오. cool-gem.gemspec 파일입니다:
Gem::Specification.new do |s|
s.author = 'Stephen Rosen'
s.name = 'cool-gem'
s.version = '0.0.1'
s.summary = 'A very cool gem that does cool stuff'
s.license = 'MIT'
s.files = []
s.add_dependency 'rails'
s.add_development_dependency 'kramdown'
end
그리고 Gemfile입니다:
source 'https://rubygems.org'
gemspec
Gemfile의 gemspec 지시어는 로컬에서 사용할 수 있는 cool-gem.gemspec 파일에 정의된 로컬 패키지 cool-gem에 대한 의존성을 선언합니다. 또한 암묵적으로 모든 개발 의존성을 development라는 의존성 그룹에 추가합니다.
따라서 이 경우 gemspec 지시어는 다음 Gemfile 내용과 동등합니다:
gem 'cool-gem', :path => '.'
group :development do
gem 'kramdown'
end
부록 B: Python의 선행 사례
Dependency Groups에 관한 기존 표준이 없는 상황에서, 잘 알려진 두 워크플로 도구인 PDM과 Poetry는 자체적인 해결책을 정의했습니다.
이 절에서는 Python에서 Dependency Groups를 정의하고 사용하는 것과 관련한 선행 사례로서 주로 이 두 도구에 초점을 맞춥니다.
프로젝트는 패키지입니다
PDM과 Poetry는 자신이 지원하는 프로젝트를 패키지로 취급합니다. 이를 통해 필요한 일부 작업에 표준 pyproject.toml 메타데이터를 사용하고 상호 작용할 수 있으며, 빌드 백엔드를 사용하여 빌드하고 설치함으로써 “현재 프로젝트”의 설치를 지원할 수 있습니다.
사실상 이는 Poetry와 PDM 어느 쪽도 패키지가 아닌 프로젝트를 지원하지 않는다는 의미입니다.
비표준 의존성 지정자
PDM과 Poetry는 공통 표준의 일부가 아닌 추가 기능으로 PEP 508 의존성 지정자를 확장합니다. 그러나 이 두 도구는 이러한 문제에 대해 서로 약간 다른 접근 방식을 사용합니다.
PDM은 pip install에 대한 인자 집합처럼 보이는 구문을 통해 로컬 경로와 편집 가능 설치를 지정할 수 있도록 지원합니다. 예를 들어, 다음 의존성 그룹에는 편집 가능 모드의 로컬 패키지가 포함되어 있습니다:
[tool.pdm.dev-dependencies]
mygroup = ["-e file:///${PROJECT_ROOT}/foo"]
이는 foo 디렉터리에서 로컬 편집 가능 설치를 포함하는 mygroup 의존성 그룹을 선언합니다.
Poetry는 패키지 이름을 지정자에 매핑하는 테이블로 의존성 그룹을 설명합니다. 예를 들어, 위의 mygroup 예제와 동일한 구성이 Poetry에서는 다음과 같이 나타날 수 있습니다:
[tool.poetry.group.mygroup]
foo = { path = "foo", editable = true }
PDM은 문자열 구문으로 제한하는 반면, Poetry는 의존성을 설명하는 테이블을 도입합니다.
의존성 그룹 설치 및 참조
PDM과 Poetry 모두 의존성 그룹 설치를 위한 도구별 지원을 제공합니다. 두 프로젝트 모두 자체 잠금 파일 형식을 지원하므로, 의존성 그룹 이름을 사용하여 해당 그룹의 잠긴 의존성 데이터를 투명하게 참조할 수 있는 기능도 모두 제공합니다.
그러나 두 도구의 의존성 그룹은 tox, nox, 또는 pip와 같은 다른 도구에서 기본적으로 참조할 수 없습니다. 예를 들어 tox에서 의존성 그룹을 설치하려면 PDM 또는 Poetry를 명시적으로 호출하여 해당 도구의 의존성 데이터를 구문 분석하고 관련 설치 단계를 수행해야 합니다.
부록 C: 사용 사례
웹 애플리케이션
웹 애플리케이션(예: Django 또는 Flask 앱)은 배포 패키지를 빌드할 필요가 없는 경우가 많지만, 소스 코드를 배포 도구 체계에 묶어 전달합니다.
예를 들어 소스 코드 저장소는 Python 패키징 메타데이터뿐 아니라 컨테이너화 또는 기타 빌드 파이프라인 메타데이터(Dockerfile, 등)를 정의할 수 있습니다. Python 애플리케이션은 전체 저장소를 빌드 컨텍스트에 복사하고, 의존성을 설치한 다음, 그 결과를 머신 이미지 또는 컨테이너로 묶어 빌드합니다.
이러한 애플리케이션에는 빌드를 위한 의존성 그룹뿐 아니라 린팅, 테스트 등을 위한 의존성 그룹도 있습니다. 실제로 오늘날 이러한 애플리케이션은 의존성 그룹을 관리하기 위해 패키징 도구와 extras와 같은 메커니즘을 사용할 수 있도록 스스로를 패키지로 정의하는 경우가 많습니다. 그러나 개념적으로는 패키지가 아니며, sdist 또는 wheel 형식으로 배포하기 위한 것도 아닙니다.
의존성 그룹을 사용하면 이러한 애플리케이션은 패키징 메타데이터에 의존하지 않고 다양한 의존성을 정의할 수 있으며, 필요한 사항을 패키징 용어로 표현하려고 하지 않아도 됩니다.
라이브러리
라이브러리는 배포 패키지(sdist 및 wheel)를 빌드하여 PyPI에 게시하는 Python 패키지입니다.
라이브러리의 경우 의존성 그룹은 개발 의존성 그룹을 정의할 때 extras를 대신하는 방법이며, 위에서 설명한 중요한 장점을 제공합니다.
라이브러리는 테스트와 타입 검사를 가능하게 하는 test와 typing그룹을 정의할 수 있으므로, 라이브러리 자체의 의존성([project.dependencies]에 지정됨)에 의존합니다.
기타 개발 요구 사항에는 패키지를 전혀 설치할 필요가 없을 수도 있습니다. 예를 들어 lint 의존성 그룹은 라이브러리 없이 설치하는 것이 적합하고 더 빠를 수 있는데, black, ruff, 또는 flake8과 같은 도구만 설치하기 때문입니다.
lint 및 test 환경은 IDE 또는 편집기 지원을 연결하는 유용한 위치가 될 수도 있습니다. 이러한 사용 방식에 대한 자세한 설명은 아래 사례를 참조하십시오.
다음은 라이브러리에 적합할 수 있는 의존성 그룹 표의 예입니다.
[dependency-groups]
test = ["pytest<8", "coverage"]
typing = ["mypy==1.7.1", "types-requests"]
lint = ["black", "flake8"]
typing-test = [{include-group = "typing"}, "pytest<8"]
이들 중 어느 것도 라이브러리 자체를 암묵적으로 설치하지 않는다는 점에 유의하십시오. 따라서 test의 경우처럼 필요할 때 모든 환경 관리 도구 체계가 라이브러리와 함께 적절한 의존성 그룹을 설치할 책임이 있습니다.
데이터 과학 프로젝트
데이터 과학 프로젝트는 일반적으로 공통 도구 체계를 사용하여 데이터를 처리하고 분석하는 스크립트와 유틸리티의 논리적 모음 형태를 취합니다. 구성 요소는 Jupyter Notebook 형식(ipynb)으로 정의될 수 있지만, 동일한 공통 핵심 유틸리티 집합에 의존합니다.
이러한 프로젝트에는 빌드하거나 설치할 패키지가 없습니다. 따라서 현재 pyproject.toml은 의존성 관리 또는 선언을 위한 해결책을 제공하지 않습니다.
이러한 프로젝트에서 적어도 하나의 주요 의존성 그룹을 정의할 수 있으면 유용합니다. 예를 들면 다음과 같습니다.
[dependency-groups]
main = ["numpy", "pandas", "matplotlib"]
그러나 다양한 스크립트에 추가적인 지원 도구가 필요할 수도 있습니다. 프로젝트가 시간이 지남에 따라 발전하면서 서로 다른 구성 요소에 대해 충돌하거나 호환되지 않는 도구 또는 도구 버전을 사용하게 될 수도 있습니다.
다음과 같이 더 정교한 구성을 고려하십시오:
[dependency-groups]
main = ["numpy", "pandas", "matplotlib"]
scikit = [{include-group = "main"}, "scikit-learn==1.3.2"]
scikit-old = [{include-group = "main"}, "scikit-learn==0.24.2"]
이는 scikit및 scikit-old를 공통 의존성 모음의 서로 유사한 두 변형으로 정의하며, 서로 다른 스크립트에 맞도록 scikit-learn의 서로 다른 버전을 가져옵니다.
이 PEP는 이러한 데이터만 정의합니다. 데이터 과학 프로젝트(또는 다른 유형의 프로젝트)가 알려진 환경에 의존성을 설치하거나 해당 환경을 다양한 스크립트와 연결하는 메커니즘은 공식화하지 않습니다. 이러한 데이터 조합을 해결하는 일은 도구 작성자의 문제로 남겨 두며, 향후 표준화될 수도 있습니다.
락파일 생성
현재 Python 생태계에는 락파일을 생성하는 도구가 여러 개 있습니다. PDM과 Poetry는 각각 자체 락파일 형식을 사용하며, pip-tools는 버전 고정 및 해시가 포함된 requirements.txt 파일을 생성합니다.
의존성 그룹은 필요한 기능을 많이 갖추지 못하므로 락파일을 저장하기에 적절한 장소가 아닙니다. 특히 대부분의 락파일 사용자가 필수적이라고 여기는 해시를 저장할 수 없습니다.
그러나 의존성 그룹은 락파일을 생성하는 도구에 유효한 입력입니다. 또한 PDM과 Poetry는 모두 의존성 그룹에 대한 자체 개념에 따라 의존성 그룹 이름을 해당 잠긴 변형을 가리키는 데 사용할 수 있도록 허용합니다.
따라서 여기서 $TOOL이라고 부르는 락파일 생성 도구를 고려하십시오. 다음과 같이 사용할 수 있습니다.
$TOOL lock --dependency-group=test
$TOOL install --dependency-group=test --use-locked
이러한 도구가 해야 하는 일은 그러한 사용을 지원하기 위해 락파일 데이터에 test 이름이 기록되도록 보장하는 것뿐입니다.
의존성 그룹 간의 상호 호환성은 보장되지 않습니다. 예를 들어, 위의 데이터 과학 예제는 scikit-learn의 충돌하는 버전을 보여 줍니다. 따라서 여러 잠긴 의존성 그룹을 함께 설치하려면 도구가 추가 제약 조건을 적용하거나 추가 락파일 데이터를 생성해야 할 수 있습니다. 이러한 문제는 이 PEP의 범위 밖으로 간주합니다.
조합을 잠그는 방법의 두 가지 예는 다음과 같습니다.
- 도구는 유효한 것으로 간주할 조합마다 락파일 데이터를 명시적으로 생성하도록 요구할 수 있습니다
- Poetry는 모든 의존성 그룹이 상호 호환되어야 한다는 요구 사항을 구현하며, 잠긴 버전을 하나만 생성합니다. (즉, 해법 집합이나 해법 행렬이 아니라 단일 해법을 찾습니다.)
환경 관리자 입력
tox, Nox 및 Hatch에서 흔히 사용하는 방식은 테스트 환경에 의존성 집합을 설치하는 것입니다.
예를 들어 tox.ini 아래에서 타입 검사 의존성을 인라인으로 정의할 수 있습니다.
[testenv:typing]
deps =
pyright
useful-types
commands = pyright src/
이러한 조합은 제한된 컨텍스트 내에서 바람직한 개발자 경험을 제공합니다. 관련 환경 관리자에서는 테스트 환경에 필요한 의존성을 해당 의존성이 필요한 명령과 함께 선언합니다. 이는 extras와 달리 패키지 메타데이터에 게시되지 않으며, 관련 환경을 구축하는 데 필요한 도구가 이를 검색할 수 있습니다.
의존성 그룹은 도구별 위치에 있는 이러한 요구 사항 데이터를 더 널리 사용 가능한 위치로 사실상 “끌어올림”으로써 이러한 사용에 적용됩니다. 위의 예에서는 tox만 선언된 의존성 목록에 액세스할 수 있습니다. 의존성 그룹을 지원하는 구현에서는 동일한 데이터를 의존성 그룹에서 사용할 수 있을 수 있습니다:
[dependency-groups]
typing = ["pyright", "useful-types"]
그러면 해당 데이터를 여러 도구에서 사용할 수 있습니다. 예를 들어, tox는 위의 deps 사용을 대체하여 dependency_groups = typing으로 지원을 구현할 수 있습니다.
의존성 그룹이 환경 관리자 사용자에게 실행 가능한 대안이 되려면, 환경 관리자는 인라인 의존성 선언을 지원하는 방식과 유사하게 의존성 그룹 처리를 지원해야 합니다.
요구 사항 데이터의 IDE 및 편집기 사용
IDE 및 편집기 통합은 통합에 사용되는 의존성 그룹에 대해 관례적이거나 구성 가능한 이름 정의를 통해 이점을 얻을 수 있습니다.
편집기 또는 IDE가 프로젝트의 공개되지 않은 의존성을 발견할 수 있는 기능을 갖추는 것이 유용한 경우는 적어도 두 가지 알려져 있습니다.
- 테스트: VS Code와 같은 IDE는 특정 테스트를 실행하기 위한 GUI 인터페이스를 지원합니다.
- 린팅: 편집기와 IDE는 오류를 강조 표시하거나 자동 수정하는 린팅 및 자동 서식 지정 통합을 지원하는 경우가 많습니다.
이러한 사례는 test, lint, fix와 같은 관례적인 그룹 이름을 정의하거나, 의존성 그룹을 선택할 수 있는 구성 메커니즘을 정의하여 처리할 수 있습니다.
예를 들어, 다음 pyproject.toml은 앞서 언급한 세 그룹을 선언합니다.
[dependency-groups]
test = ["pytest", "pytest-timeout"]
lint = ["flake8", "mypy"]
fix = ["black", "isort", "pyupgrade"]
이 PEP는 이러한 이름을 표준화하거나 그러한 용도로 예약하려는 시도를 전혀 하지 않습니다. IDE는 다양한 목적에 사용되는 그룹 이름을 표준화할 수도 있고, 사용자가 구성하도록 허용할 수도 있습니다.
이 선언을 통해 프로젝트 작성자가 프로젝트에 적합한 도구에 대해 알고 있는 내용을 해당 프로젝트의 모든 편집기와 공유할 수 있습니다.
Copyright
This document is placed in the public domain or under the CC0-1.0-Universal license, whichever is more permissive.