PEP 633 – 분해된 TOML 테이블을 사용한 pyproject.toml의 의존성 명세
- Author:
- Laurie Opperman <laurie_opperman at hotmail.com>, Arun Babu Neelicattu <arun.neelicattu at gmail.com>
- Sponsor:
- Brett Cannon <brett at python.org>
- Discussions-To:
- Discourse thread
- Status:
- Rejected
- Type:
- Standards Track
- Topic:
- Packaging
- Created:
- 02-Sep-2020
- Post-History:
- 02-Sep-2020
- Resolution:
- Discourse message
Table of Contents
번역·라이선스 안내
이 비공식 한국어 번역은 원문 Copyright 절의 Public Domain or CC0-1.0, whichever is more permissive 조건에 따라 제공합니다. 원저자와 공식 원문은 그대로 표시합니다. 수정되지 않은 기준 원문 · 공식 최신판
거부 통지
이 PEP는 인기, 기존 PEP 508 문자열 사용 방식과의 일관성, 기존 패키징 도구 모음과의 호환성 때문에 PEP 631을 대신하여 거부되었습니다.
초록
이 PEP는 PEP 631에서 정의한 PEP 508 기반 접근 방식의 대안으로, 패키징 관련 도구가 사용할 수 있도록 PEP 621에 정의된 필드를 사용하여 pyproject.toml 파일에 프로젝트의 의존성을 작성하는 방법을 규정합니다.
동기
요구 사항을 PEP 508 문자열 대신 TOML 테이블 및 기타 데이터 형식으로 표현하면 여러 이점이 있습니다:
- TOML 구문을 통한 손쉬운 초기 검증이 가능합니다.
- 예를 들어 JSON schema를 사용하는 스키마를 통한 손쉬운 2차 검증이 가능합니다.
- 구문을 암기하는 대신 사용자가 주어진 기능의 키를 추측할 수 있습니다.
- 여러 다른 인기 언어의 사용자들은 TOML 구문에 이미 익숙할 수 있습니다.
- TOML은 JSON과 동일한 데이터 구조를 직접 표현하며 따라서 Python 리터럴의 부분 집합이기도 하므로, 사용자는 값의 계층 구조와 유형을 이해할 수 있습니다.
근거
이 내용의 대부분은 PEP 621 dependencies topic에서 이루어진 논의에서 가져왔습니다. 여기에는 Pipfile, Poetry, Dart’s dependencies, Rust’s Cargo의 요소가 포함되어 있습니다. comparison document는 이 형식과 PEP 508 스타일 지정자 간의 장단점을 보여 줍니다.
동일한 배포 패키지 이름을 사용하는 여러 요구 사항을 명세할 때(환경 마커가 적절한 의존성을 선택하는 경우), 선택된 해법은 요구 사항 배열을 허용하는 Poetry의 방식과 유사합니다.
직접 참조 키는 PEP 610 및 PEP 440과 긴밀하게 부합하고 이를 활용하므로 패키징 생태계의 차이를 줄이며, 명세에 관한 기존 작업에 의존합니다.
명세
앞서 PEP 621에서와 같이, 메타데이터가 올바르게 지정되지 않은 경우 도구는 오류를 발생시켜야 합니다. 메타데이터는 TOML 명세를 반드시 준수해야 합니다.
이 문서가 의존성을 지정하기 위한 명세라는 점에서 발생하는 혼동을 줄이기 위해 “요구 사항”이라는 단어는 PEP 508 의존성 명세를 의미하는 데 사용합니다.
다음 테이블이 PEP 621에 지정된 project테이블에 추가됩니다.
dependencies
형식: 테이블
이 테이블 내부의 키는 필요한 배포 패키지의 이름입니다. 값은 다음 유형 중 하나일 수 있습니다.
- 문자열: 요구 사항이 버전 요구 사항으로만 정의되며, 요구 사항 테이블의
version과 동일한 명세를 사용합니다. 단, 빈 문자열""을 허용하여 버전에 제한을 두지 않을 수 있습니다. - 테이블: 요구 사항 테이블입니다.
- 배열: 요구 사항 테이블의 배열입니다. 빈 배열
[]을 값으로 지정하면 오류입니다.
요구 사항 테이블
요구 사항 테이블의 키는 다음과 같습니다(모두 선택 사항입니다):
version(문자열): 쉼표로 구분된 버전 지정자 절의 목록인 PEP 440 버전 지정자입니다. 문자열은 반드시 비어 있지 않아야 합니다.extras(문자열 배열): 배포 패키지의 PEP 508 extras 선언 목록입니다. 목록은 반드시 비어 있지 않아야 합니다.markers(문자열): PEP 508 환경 마커 표현식입니다. 문자열은 반드시 비어 있지 않아야 합니다.url(문자열): 요구 사항을 충족하도록 설치할 아티팩트의 URL입니다.file://은 로컬 파일 시스템에서 가져올 패키지에 사용되는 접두사임에 유의하십시오.git,hg,bzr또는svn(문자열): 복제할 VCS 저장소의 URL이며, 해당 저장소의 트리는 요구 사항을 충족하도록 설치됩니다(PEP 440에 지정된 대로). 추가 VCS 키는 PEP 610에 대한 수정안을 통해 추가되지만, 도구는 수정안이 수락되기 전에 명령줄 명령을 사용하여 다른 VCS를 지원하도록 선택할 수 있습니다.revision(문자열): 설치 전에 지정된 VCS 저장소에서 체크아웃할 특정 리비전의 식별자입니다. 사용자는git,hg,bzr,svn또는 다른 VCS 키 중 하나를 사용하여 설치할 배포 패키지를 식별하는 경우에만 이를 제공해야 합니다. 리비전 식별자는 PEP 610에서 제안됩니다.
다음 키 중 동시에 지정할 수 있는 것은 최대 하나이며, 이는 요구 사항에서 논리적으로 서로 충돌하기 때문입니다: version, url, git, hg, bzr, svn 및 기타 모든 VCS 키.
빈 요구 사항 테이블 {}은 빈 문자열 ""과 마찬가지로 요구 사항에 아무런 제한을 두지 않습니다.
이 문서에 지정되지 않은 키가 제공되면 구문 분석 시 반드시 오류가 발생해야 합니다.
optional-dependencies
형식: 테이블
이 테이블 내부의 키는 extra에 필요한 배포 패키지의 이름입니다. 값은 다음 유형 중 하나일 수 있습니다.
- 테이블: 요구 사항 테이블입니다.
- 배열: 요구 사항 테이블의 배열입니다.
이러한 요구 사항 테이블은 the same specification as above과 동일한 사양을 따르며, 다음 필수 키가 추가됩니다:
for-extra(문자열): 이 요구 사항이 필요한 PEP 508 extra의 이름입니다.
참조 구현
도구는 이 형식을 PEP 508 요구 사항 문자열로 변환해야 합니다. 다음은 해당 변환의 예시 구현입니다(유효성 검사가 이미 수행되었다고 가정합니다):
def convert_requirement_to_pep508(name, requirement):
if isinstance(requirement, str):
requirement = {"version": requirement}
pep508 = name
if "extras" in requirement:
pep508 += " [" + ", ".join(requirement["extras"]) + "]"
if "version" in requirement:
pep508 += " " + requirement["version"]
if "url" in requirement:
pep508 += " @ " + requirement["url"]
for vcs in ("git", "hg", "bzr", "svn"):
if vcs in requirement:
pep508 += " @ " + vcs + "+" + requirement[vcs]
if "revision" in requirement:
pep508 += "@" + requirement["revision"]
extra = None
if "for-extra" in requirement:
extra = requirement["for-extra"]
if "markers" in requirement:
markers = requirement["markers"]
if extra:
markers = "extra = '" + extra + "' and (" + markers + ")"
pep508 += "; " + markers
return pep508, extra
def convert_requirements_to_pep508(dependencies):
pep508s = []
extras = set()
for name, req in dependencies.items():
if isinstance(req, list):
for sub_req in req:
pep508, extra = convert_requirement_to_pep508(name, sub_req)
pep508s.append(pep508)
if extra:
extras.add(extra)
else:
pep508, extra = convert_requirement_to_pep508(name, req)
pep508s.append(pep508)
if extra:
extras.add(extra)
return pep508s, extras
def convert_project_requirements_to_pep508(project):
reqs, _ = convert_requirements_to_pep508(project.get("dependencies", {}))
optional_reqs, extras = convert_requirements_to_pep508(
project.get("optional-dependencies", {})
)
reqs += optional_reqs
return reqs, extras
JSON 스키마
초기 유효성 검사를 위해 JSON 스키마를 사용할 수 있습니다. 이는 도구가 일관된 유효성 검사를 수행하는 데 도움이 될 뿐만 아니라, 사용자가 의존성 목록을 작성하는 동안 코드 편집기가 유효성 검사 오류를 강조 표시할 수 있게 합니다.
{
"$id": "spam",
"$schema": "http://json-schema.org/draft-07/schema#",
"title": "Project metadata",
"type": "object",
"definitions": {
"requirementTable": {
"title": "Full project dependency specification",
"type": "object",
"properties": {
"extras": {
"title": "Dependency extras",
"type": "array",
"items": {
"title": "Dependency extra",
"type": "string"
}
},
"markers": {
"title": "Dependency environment markers",
"type": "string"
}
},
"propertyNames": {
"enum": [
"extras",
"markers",
"version",
"url",
"git",
"hg",
"bzr",
"svn",
"for-extra"
]
},
"oneOf": [
{
"title": "Version requirement",
"properties": {
"version": {
"title": "Version",
"type": "string"
}
}
},
{
"title": "URL requirement",
"properties": {
"url": {
"title": "URL",
"type": "string",
"format": "uri"
}
},
"required": [
"url"
]
},
{
"title": "VCS requirement",
"properties": {
"revision": {
"title": "VCS repository revision",
"type": "string"
}
},
"oneOf": [
{
"title": "Git repository",
"properties": {
"git": {
"title": "Git URL",
"type": "string",
"format": "uri"
}
},
"required": [
"git"
]
},
{
"title": "Mercurial repository",
"properties": {
"hg": {
"title": "Mercurial URL",
"type": "string",
"format": "uri"
}
},
"required": [
"hg"
]
},
{
"title": "Bazaar repository",
"properties": {
"bzr": {
"title": "Bazaar URL",
"type": "string",
"format": "uri"
}
},
"required": [
"bzr"
]
},
{
"title": "Subversion repository",
"properties": {
"svn": {
"title": "Subversion URL",
"type": "string",
"format": "uri"
}
},
"required": [
"svn"
]
}
]
}
]
},
"requirementVersion": {
"title": "Version project dependency specification",
"type": "string"
},
"requirement": {
"title": "Project dependency specification",
"oneOf": [
{
"$ref": "#/definitions/requirementVersion"
},
{
"$ref": "#/definitions/requirementTable"
},
{
"title": "Multiple specifications",
"type": "array",
"items": {
"$ref": "#/definitions/requirementTable"
},
"minLength": 1
}
]
},
"optionalRequirementTable": {
"title": "Project optional dependency specification table",
"allOf": [
{
"$ref": "#/definitions/requirementTable"
},
{
"properties": {
"for-extra": {
"title": "Dependency's extra",
"type": "string"
}
},
"required": [
"for-extra"
]
}
]
},
"optionalRequirement": {
"title": "Project optional dependency specification",
"oneOf": [
{
"$ref": "#/definitions/optionalRequirementTable"
},
{
"title": "Multiple specifications",
"type": "array",
"items": {
"$ref": "#/definitions/optionalRequirementTable"
},
"minLength": 1
}
]
}
},
"properties": {
"dependencies": {
"title": "Project dependencies",
"type": "object",
"additionalProperties": {
"$ref": "#/definitions/requirement"
}
},
"optional-dependencies": {
"title": "Project dependencies",
"type": "object",
"additionalProperties": {
"$ref": "#/definitions/optionalRequirement"
}
}
}
}
예시
완전한 인공적 예제:
[project.dependencies]
flask = { }
django = { }
requests = { version = ">= 2.8.1, == 2.8.*", extras = ["security", "tests"], markers = "python_version < '2.7'" }
pip = { url = "https://github.com/pypa/pip/archive/1.3.1.zip" }
sphinx = { git = "ssh://git@github.com/sphinx-doc/sphinx.git" }
numpy = "~=1.18"
pytest = [
{ version = "<6", markers = "python_version < '3.5'" },
{ version = ">=6", markers = "python_version >= '3.5'" },
]
[project.optional-dependencies]
pytest-timout = { for-extra = "dev" }
pytest-mock = [
{ version = "<6", markers = "python_version < '3.5'", for-extra = "dev" },
{ version = ">=6", markers = "python_version >= '3.5'", for-extra = "dev" },
]
다음은 PEP 631에 대한 경의의 표시로, docker-compose에 대한 동등한 의존성 명세입니다:
[project.dependencies]
cached-property = ">= 1.2.0, < 2"
distro = ">= 1.2.0, < 2"
docker = { extras = ["ssh"], version = ">= 4.2.2, < 5" }
docopt = ">= 0.6.1, < 1"
jsonschema = ">= 2.5.1, < 4"
PyYAML = ">= 3.10, < 6"
python-dotenv = ">= 0.13.0, < 1"
requests = ">= 2.20.0, < 3"
texttable = ">= 0.9.0, < 2"
websocket-client = ">= 0.32.0, < 1"
# Conditional
"backports.shutil_get_terminal_size" = { version = "== 1.0.0", markers = "python_version < '3.3'" }
"backports.ssl_match_hostname" = { version = ">= 3.5, < 4", markers = "python_version < '3.5'" }
colorama = { version = ">= 0.4, < 1", markers = "sys_platform == 'win32'" }
enum34 = { version = ">= 1.0.4, < 2", markers = "python_version < '3.4'" }
ipaddress = { version = ">= 1.0.16, < 2", markers = "python_version < '3.3'" }
subprocess32 = { version = ">= 3.5.4, < 4", markers = "python_version < '3.2'" }
[project.optional-dependencies]
PySocks = { version = ">= 1.5.6, != 1.5.7, < 2", for-extra = "socks" }
ddt = { version = ">= 1.2.2, < 2", for-extra = "tests" }
pytest = { version = "< 6", for-extra = "tests" }
mock = { version = ">= 1.0.1, < 4", markers = "python_version < '3.4'", for-extra = "tests" }
호환성 예제
이 PEP의 저자들은 다양한 도구가 종속성 명세를 위해 이 형식을 읽고 쓸 필요가 있다는 점을 인식하고 있습니다. 이 절은 현재 사용되는 표준인 PEP 508과의 직접적인 비교와 상호 변환 예제를 제공하는 것을 목표로 합니다.
Note
단순성과 명확성을 위해, TOML이 각 명세를 지정할 수 있도록 허용하는 다양한 방식은 표현되지 않았습니다. 이 예제들은 표준 인라인 표현을 사용합니다.
예를 들어, 다음은 TOML에서 동등하다고 간주되지만, 이 절의 예제에서는 두 번째 형식을 선택합니다.
aiohttp.version = "== 3.6.2"
aiohttp = { version = "== 3.6.2" }
버전이 제한된 종속성
버전 제약 없음
aiohttp
aiohttp = {}
단순 버전 제약
aiohttp >= 3.6.2, < 4.0.0
aiohttp = { version = ">= 3.6.2, < 4.0.0" }
Note
이는 간결성을 위해 문자열로도 표현될 수 있습니다.
aiohttp = ">= 3.6.2, < 4.0.0"
직접 참조 종속성
URL 종속성
aiohttp @ https://files.pythonhosted.org/packages/97/d1/1cc7a1f84097d7abdc6c09ee8d2260366f081f8e82da36ebb22a25cdda9f/aiohttp-3.6.2-cp35-cp35m-macosx_10_13_x86_64.whl
aiohttp = { url = "https://files.pythonhosted.org/packages/97/d1/1cc7a1f84097d7abdc6c09ee8d2260366f081f8e82da36ebb22a25cdda9f/aiohttp-3.6.2-cp35-cp35m-macosx_10_13_x86_64.whl" }
VCS 종속성
aiohttp @ git+ssh://git@github.com/aio-libs/aiohttp.git@master
aiohttp = { git = "ssh://git@github.com/aio-libs/aiohttp.git", revision = "master" }
환경 마커
aiohttp >= 3.6.1; python_version >= '3.8'
aiohttp = { version = ">= 3.6.1", markers = "python_version >= '3.8'" }
위 예제를 약간 확장한 것으로, 인터프리터 버전에 따라 특정 버전의 aiohttp가 필요한 경우입니다.
aiohttp >= 3.6.1; python_version >= '3.8'
aiohttp >= 3.0.0, < 3.6.1; python_version < '3.8'
aiohttp = [
{ version = ">= 3.6.1", markers = "python_version >= '3.8'" },
{ version = ">= 3.0.0, < 3.6.1", markers = "python_version < '3.8'" }
]
패키지 엑스트라
패키지 엑스트라에 대한 종속성 지정
aiohttp >= 3.6.2; extra == 'http'
aiohttp = { version = ">= 3.6.2", for-extra = "http" }
종속성으로부터 엑스트라 사용하기
aiohttp [speedups] >= 3.6.2
aiohttp = { version = ">= 3.6.2", extras = ["speedups"] }
복합 예제
버전 제약
aiohttp [speedups] >= 3.6.2; python_version >= '3.8' and extra == 'http'
aiohttp = { version = ">= 3.6.2", extras = ["speedups"], markers = "python_version >= '3.8'", for-extra = "http" }
직접 참조 (VCS)
aiohttp [speedups] @ git+ssh://git@github.com/aio-libs/aiohttp.git@master ; python_version >= '3.8' and extra == 'http'
aiohttp = { git = "ssh://git@github.com/aio-libs/aiohttp.git", revision = "master", extras = ["speedups"], markers = "python_version >= '3.8'", for-extra = "http" }
거부된 아이디어
dependencies를 배열로 전환
각 요소가 (name 키를 가진) 테이블만 되도록 하고 요구사항 테이블의 배열이 없도록, 테이블 대신 배열을 사용합니다. 이는 TOML 형식에서 매우 장황하고 제한적이었으며, 주어진 배포 패키지에 대해 여러 요구사항을 갖는 경우는 흔하지 않습니다.
optional-dependencies를 extras로 교체
optional-dependencies 테이블을 제거하는 대신, 요구사항에 optional 키를 포함하고 프로젝트의 엑스트라에 필요한 (선택적) 요구사항을 지정하는 extras 테이블을 함께 도입합니다. 이는 동일한 명세를 가진 테이블의 수를 (1개로) 줄이고 요구사항을 한 번만 지정하되 여러 엑스트라에서 사용할 수 있게 하지만, 요구사항의 일부 속성(어떤 엑스트라에 속하는지)을 멀어지게 하고, 필수 종속성과 선택적 종속성을 (혼합될 수 있는 형태로) 함께 묶으며, 배포 패키지가 여러 요구사항을 가질 때 요구사항을 선택할 간단한 방법이 없을 수 있습니다. optional-dependencies가 이미 PEP 621 초안에서 사용되었기 때문에 이는 거부되었습니다.
요구사항 내의 direct 테이블
직접 참조 키들을 direct 테이블에 포함시키고, VCS는 vcs 키의 값으로 지정하도록 합니다. 이는 더 명시적이고 JSON 스키마 검증에 포함시키기 쉬웠지만, 너무 장황하고 가독성이 떨어지는 것으로 판단되었습니다.
해시 포함
직접 참조 요구사항에 해시를 포함합니다. 이는 패키지 락 파일(lock-file)에만 해당되었으며, 프로젝트의 메타데이터에는 딱히 위치가 없었습니다.
각 extra별 의존성 테이블
optional-dependencies를 각 extra별 의존성 테이블들의 테이블로 만들고, 테이블 이름을 해당 extra의 이름으로 합니다. 이는 optional-dependencies를 dependencies(요구사항의 테이블)와 다른 타입(요구사항 테이블들의 테이블)으로 만들게 되며, 이는 사용자에게 낯설고 파싱하기도 더 어려울 수 있습니다.
환경 마커 키
각 PEP 508 환경 마커를 요구사항 내의 키(또는 자식 테이블 키)로 만듭니다. 이는 가독성과 파싱 용이성을 높인다고 볼 수 있습니다. markers 키는 더 고급 명세를 위해 여전히 허용되며, 키로 지정된 환경 마커들은 이 키의 결과와 and로 결합됩니다. 이는 더 많은 설계가 이루어져야 한다는 이유로 보류되었습니다.
하나의 요구사항이 충족시킬 수 있는 여러 extra
for-extra 키를 for-extras로 대체하고, 값을 해당 요구사항이 충족시키는 extra들의 배열로 합니다. 이는 일부 중복을 줄이지만, 이 경우 그 중복이 어떤 extra가 어떤 의존성을 갖는지를 명시적으로 드러냅니다.
Copyright
This document is placed in the public domain or under the CC0-1.0-Universal license, whichever is more permissive.