Following system colour scheme Selected dark colour scheme Selected light colour scheme

Python 개선 제안 한국어 번역

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 명세를 준수해야 합니다.

이 문서가 의존성 명시 방법에 관한 명세라서 생기는 혼동을 줄이기 위해, “요구 사항(requirement)”이라는 단어는 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의 명령줄 명령을 사용하여 다른 VCS를 지원하도록 선택할 수 있습니다.
  • revision (문자열): 설치 전에 체크아웃할, 지정된 VCS 저장소의 특정 리비전 식별자입니다. 사용자는 설치할 배포 패키지를 식별하는 데 git, hg, bzr, svn 또는 다른 VCS 키 중 하나를 사용하는 경우에만 이 키를 제공해야 합니다. 리비전 식별자는 PEP 610에 제안되어 있습니다.

다음 키들은 요구 사항 안에서 논리적으로 서로 충돌하므로, 동시에 최대 하나만 지정할 수 있습니다: version, url, git, hg, bzr, svn 및 기타 모든 VCS 키.

빈 문자열 ""뿐만 아니라 빈 요구 사항 테이블 {}도 요구 사항에 아무런 제한을 두지 않습니다.

이 문서에 명시되지 않은 키가 제공되면 반드시 파싱 오류가 발생해야 합니다.

optional-dependencies

형식: 테이블

이 테이블 안의 키는 extra에 필요한 배포 패키지의 이름입니다. 값은 다음 타입 중 하나일 수 있습니다:

  • 테이블: 요구 사항 테이블입니다.
  • 배열: 요구 사항 테이블의 배열입니다.

이 요구 사항 테이블은 위와 같은 명세를 따르며, 다음 필수 키가 추가됩니다:

  • 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가 어떤 의존성을 가지는지가 명시적으로 드러납니다.