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

Python 개선 제안 한국어 번역

PEP 665 – 애플리케이션의 재현성을 위해 Python 의존성을 나열하는 파일 형식

Author:
Brett Cannon <brett at python.org>, Pradyun Gedam <pradyunsg at gmail.com>, Tzu-ping Chung <uranusjr at gmail.com>
PEP-Delegate:
Paul Moore <p.f.moore at gmail.com>
Discussions-To:
Discourse thread
Status:
Rejected
Type:
Standards Track
Topic:
Packaging
Created:
29-Jul-2021
Post-History:
29-Jul-2021, 03-Nov-2021, 25-Nov-2021
Superseded-By:
751
Resolution:
Discourse message

Table of Contents

번역·라이선스 안내

이 비공식 한국어 번역은 원문 Copyright 절의 Public Domain or CC0-1.0, whichever is more permissive 조건에 따라 제공합니다. 원저자와 공식 원문은 그대로 표시합니다. 수정되지 않은 기준 원문 · 공식 최신판

Note

이 PEP는 소스 배포판 지원이 없다는 점 때문에 커뮤니티로부터 미온적인 반응을 얻어 거부되었습니다.

초록

이 PEP는 애플리케이션의 Python 패키지 설치 요구 사항 목록과 명시된 요구 사항 간의 관계를 명시하는 파일 형식을 규정합니다. 요구 사항 목록은 설치 대상에 대해 빠짐없는 것으로 간주되므로, 설치 대상 플랫폼과 파일 자체 외에는 어떠한 정보도 필요로 하지 않습니다. 이 파일 형식은 서로 다른 플랫폼에 요구 사항을 설치할 수 있을 만큼 유연하므로, 같은 파일로 여러 플랫폼에서 재현성을 확보할 수 있습니다.

용어

이 PEP의 주제에 대한 논의를 원활하게 하려면 정의에 합의해야 하는 용어가 몇 가지 있습니다.

패키지는 의존성으로 설치하여 임포트 시스템을 통해 사용하는 것입니다. PyPI에 있는 패키지들이 그 예입니다.

애플리케이션 또는 앱은 다른 외부 코드가 임포트 시스템을 통해 직접 의존하지 않는 최종 산출물입니다(즉, 독립 실행형입니다). 데스크톱 애플리케이션, 명령줄 도구 등이 애플리케이션의 예입니다.

잠금 파일은 앱을 위해 설치할 패키지를 기록합니다. 전통적으로 설치할 패키지의 정확한 버전은 잠금 파일에 명시되지만, 명시된 패키지가 특정 플랫폼에 항상 설치되는 것은 아니며(뒤의 절에서 설명하는 필터링 로직에 따릅니다), 이 덕분에 잠금 파일은 여러 플랫폼에 걸친 재현성을 기술할 수 있습니다. 그 예로 npm의 package-lock.json, Poetry의 Poetry.lock 등이 있습니다.

잠금은 앱이 의존하는 패키지를 입력으로 받아 이로부터 잠금 파일을 생성하는 행위입니다.

잠금 도구는 잠금 파일을 생성하는 도구입니다.

설치 프로그램은 잠금 파일을 사용하여 잠금 파일이 명시하는 것을 설치합니다.

동기

애플리케이션이 재현 가능한 설치를 원하는 데에는 몇 가지 이유가 있습니다(패키지 개발, Python 애플리케이션 외부에서 의존성 잠금을 처리하는 더 큰 시스템으로의 통합, 또는 엄격하고 재현 가능한 설치보다 유연한 설치 요구 사항이 선호되는 기타 상황은 여기서 고려하지 않습니다).

첫째, 재현성은 개발을 수월하게 합니다. 여러분과 동료 개발자 모두가 특정 플랫폼에서 결국 같은 파일을 갖게 되면, 모두가 애플리케이션에 대해 같은 경험을 목표로 개발하고 있음을 보장할 수 있습니다. 또한 여러분이 사용자를 위해 개발한 것과 같은 경험을 보장하려면, 사용자가 여러분이 예상한 것과 같은 파일을 설치하기를 원할 것입니다.

둘째, 여러 플랫폼에 걸쳐 설치되는 것을 재현할 수 있기를 원합니다. 운영 체제, CPU 등에 걸친 Python의 이식성 덕분에, 단일 플랫폼에 제한되지 않는 애플리케이션을 만드는 것은 매우 쉬우며 바람직한 경우도 많습니다. 따라서 플랫폼 간에 패키지 의존성의 차이를 허용할 만큼 유연하면서도, 어느 한 특정 플랫폼에서는 일관성과 재현성을 유지하기를 원합니다.

셋째, 재현성은 보안을 강화합니다. 어떤 파일이 설치되는지를 정확히 통제하면, 악의적인 행위자가 애플리케이션에 악성 코드를 몰래 끼워 넣으려는 시도(즉, 일부 공급망 공격)가 없음을 확인할 수 있습니다. 항상 재현 가능한 설치로 이어지는 잠금 파일을 사용하면, 특정 위험을 완전히 피할 수 있습니다.

넷째, wheel file 형식에 의존하면 빌드 도구가 재현성을 직접 지원하지 않더라도 재현성을 확보할 수 있습니다. 휠은 정적이며 설치 과정에서 코드를 실행하지 않으므로 항상 재현 가능한 결과를 냅니다. 이를 소스 배포판(일명 sdist)이나 소스 트리와 비교해 보십시오. 이들은 본질적으로 코드 실행이 수반되기 때문에, 해당 빌드 도구가 재현성을 지원하는 경우에만 재현 가능한 설치로 이어집니다. 안타깝게도 대다수의 빌드 도구는 재현 가능한 빌드를 지원하지 않으므로, 이 PEP는 패키지 형식으로 휠만 지원함으로써 이 문제를 완화하는 데 도움을 줍니다.

현재의 해결책들은 앞서 제시한 목표를 충족하지 못하므로, 이 PEP는 잠금 파일에 대한 표준을 제안합니다. 오늘날 잠금 파일 표준에 가장 가까운 것은 pip의 requirements file format입니다. 안타깝게도 그 형식은 본질적으로 재현 가능한 설치로 이어지지 않습니다(요구 사항 파일과 설치 프로그램 자체 모두에 선택적 기능이 필요하며, 이는 뒤에서 논의합니다).

여러 도구가 각자 독립적으로 자체 잠금 파일 형식을 만들었다는 사실에서 알 수 있듯이, 커뮤니티 스스로도 잠금 파일의 필요성을 보여 주었습니다:

  1. PDM
  2. pip-tools
  3. Pipenv
  4. Poetry
  5. Pyflow

안타깝게도 이 도구들은 모두 서로 다른 잠금 파일 형식을 사용합니다. 이는 이 도구들을 둘러싼 주변 도구가 각 도구마다 따로 만들어져야 한다는 뜻입니다. 이는 코드 편집기나 호스팅 제공자 같은 주변 도구에 영향을 미칩니다. 이들은 사용자의 애플리케이션 코드를 받아들이는 데 있어 가능한 한 유연하기를 원하지만, 또 하나의 잠금 파일 형식을 지원하기 위해 투입할 수 있는 개발 자원에는 한계가 있습니다. 표준화된 형식이 있으면 도구들은 단일 대상에 작업을 집중할 수 있으며, 잠금 파일 형식 바깥에서 개발자가 내린 워크플로 결정이 예컨대 호스팅 제공자에게는 신경 쓸 필요가 없는 일이 되도록 할 수 있습니다.

다른 프로그래밍 언어 커뮤니티들도 이 문제에 대한 자체 해결책을 개발함으로써 잠금 파일의 유용성을 보여 주었습니다. 그러한 커뮤니티로는 다음과 같은 것들이 있습니다:

  1. Dart
  2. npm/Node
  3. Go
  4. Rust

지난 10년간 프로그래밍 언어들의 추세는 잠금 파일 해결책을 제공하는 방향으로 나아가 온 것으로 보입니다.

근거

파일 형식

저희는 잠금 파일의 변경 사항을 감사할 때 diff로 읽기 쉬운 파일 형식을 원했습니다. 이에 따라, 그리고 PEP 518과 pyproject.toml 덕분에, 저희는 TOML 파일 형식을 채택하기로 결정했습니다.

설계 단계부터의 보안

requirements file format을 현재 잠금 파일 표준에 가장 가까운 것으로 본다면, 이 파일 형식에는 보안 측면에서 몇 가지 문제가 있습니다. 첫째, 이 파일 형식은 패키지의 정확한 버전을 지정하도록 요구하지 않습니다. 요구 사항 파일 사용자들이 이를 관리하도록 돕는 pip-tools 같은 도구가 존재하는 것도 이 때문입니다.

둘째, 특정 의존성에 --hash 인자를 사용하여 어떤 파일을 설치해도 되는지 직접 지정해야 합니다. pip-tools에서도 --generate-hashes CLI 인자를 지정해야 하므로 이 역시 선택 사항입니다. 또한 검사할 해시가 없는 의존성이 하나도 없도록 하려면 pip에 --require-hashes가 필요합니다.

셋째, 어떤 파일이 설치될 수 있는지 통제하더라도 다른 패키지가 설치되는 것까지 막지는 못합니다. 요구 사항 파일에 나열되지 않은 의존성이 있으면, pip는 거리낌 없이 그 필요를 충족할 파일을 찾아 나섭니다. 요구 사항 파일 밖에서 의도하지 않은 의존성 해석이 일어나지 않도록 하려면 pip에 --no-deps를 인자로 지정해야 합니다.

넷째, 이 형식은 source distribution file (일명 “sdist”)의 설치를 허용합니다. sdist를 설치하려면 본질적으로 임의의 Python 코드를 실행해야 하며, 이는 어떤 파일이 설치될 수 있는지 통제할 수 없다는 뜻입니다. --only-binary :all:을 지정해야만 pip가 각 패키지에 wheel file만 사용하도록 보장할 수 있습니다.

정리하자면, 요구 사항 파일이 이 제안만큼 안전하려면 사용자는 항상 다음 단계를 수행해야 합니다:

  1. pip-tools와 그 명령어 pip-compile --generate-hashes를 사용하십시오
  2. pip install --require-hashes --no-deps --only-binary :all:을 사용하여 요구 사항 파일을 설치하십시오

결정적으로, 이 모든 플래그는 물론이고 pip-tools가 제공하는, 무엇을 설치할지에 대한 구체성과 완전성 역시 요구 사항 파일에서는 모두 선택 사항입니다.

따라서 이 PEP에서 제기하는 제안은 설계 단계부터 안전하며, 일부 공급망 공격에 대응합니다. 설치에 사용될 파일의 해시는 필수입니다. 파일 시스템에 어떤 파일이 배치될지 모호함 없이 정의하기 위해 휠에서만 설치할 수 있습니다. 설치 프로그램은 주어진 플랫폼에 대해 잠금 파일로부터 반드시 결정론적인 설치를 수행해야 합니다. 이 모든 것은 (잠금 파일과 그 안에 나열된 내용을 감사했다면) 신뢰할 수 있다고 판단할 수 있는 재현 가능한 설치로 이어집니다.

크로스 플랫폼

PDM과 Poetry처럼 이미 잠금 파일을 갖춘 여러 프로젝트는 크로스 플랫폼인 잠금 파일을 제공합니다. 이를 통해 하나의 잠금 파일이 여러 플랫폼에서 동작하면서도, 모든 곳에서 정확히 동일한 최상위 요구 사항이 설치되고 각 플랫폼에서의 설치는 일관되고 모호함이 없게 됩니다.

이것이 왜 유용한지 PyWeek (일주일 동안 진행되는 게임 개발 대회)와 관련된 예를 들어 살펴봅시다. 여러분은 Linux에서 개발하고 있고, 함께하기로 한 파트너는 macOS를 사용한다고 가정합시다. 이제 심사위원들은 Windows를 사용한다고 가정합시다. 플랫폼별 요구 사항(예: 어떤 패키지가 Windows에서는 보조 패키지를 필요로 하는 경우)을 허용하면서도, 모두가 동일한 최상위 의존성을 사용하도록 하려면 어떻게 해야 합니까?

크로스 플랫폼 잠금 파일이 있으면 핵심 요구 사항이 모든 플랫폼에서 일관되게 충족되도록 할 수 있습니다. 그런 다음 같은 플랫폼의 모든 사용자가 동일하게 재현 가능한 설치를 얻도록 할 수도 있습니다.

단순한 설치 프로그램

잠금 도구와 설치 프로그램 사이의 관심사 분리 덕분에 설치 프로그램이 수행할 작업은 훨씬 단순해집니다. 따라서 설치 프로그램을 더 쉽게 작성할 수 있을 뿐만 아니라, 설치 프로그램이 모호함 없고 재현 가능한 설치를 올바르게 만들어 내도록 보장하는 데에도 도움이 됩니다.

또한 설치 프로그램은 설치를 만들어 내는 데 더 적은 연산과 에너지를 소모할 수 있습니다. 설치 프로그램은 잠금 도구보다 더 자주 실행될 것으로 예상되므로, 이는 더 빠른 설치뿐만 아니라 에너지 소비 측면에서도 유익합니다.

이로 인해 설치 프로그램에 이득이 되도록 로커가 사전에 더 많은 작업을 해야 하는 설계가 되었습니다. 이는 또한 모호함을 피하기 위해 잠금 파일에서 패키지 의존성의 복잡도가 더 단순해지고 이해하기 쉬워진다는 것을 의미합니다.

명세

세부 사항

잠금 파일은 반드시 TOML 파일 형식을 사용해야 합니다. 이는 PEP 518이 pyproject.toml에 TOML을 채택했기 때문에 Python 패키징 생태계에 또 다른 파일 형식을 둘 필요를 없애 줄 뿐만 아니라, 잠금 파일을 사람이 더 읽기 쉽게 만드는 데에도 도움이 됩니다.

잠금 파일의 파일 이름은 반드시 .pylock.toml로 끝나야 합니다. .toml 부분은 파일의 형식을 모호함 없이 구별해 주며, 코드 편집기와 같은 도구가 파일을 적절히 지원하도록 돕습니다. .pylock 부분은 이 파일을 사용자가 가진 다른 TOML 파일과 구별하여, 도구가 일반적인 TOML 파일이 아닌 Python 잠금 파일에 특화된 기능을 만드는 로직을 더 쉽게 작성할 수 있도록 합니다.

다음 섹션들은 TOML 파일 데이터 형식의 최상위 키입니다. 필수로 표시되지 않은 필드는 모두 선택 사항으로 간주됩니다.

version

이 필드는 필수입니다.

사용 중인 잠금 파일의 버전입니다. 이 키는 반드시 core metadata spec의 Metadata-Version 키와 동일한 형식을 따르는 숫자로 구성된 문자열이어야 합니다.

향후 PEP가 다른 값을 허용하기 전까지 이 값은 반드시 "1.0"으로 설정해야 합니다. 파일 형식에 새로운 선택적 키를 도입하는 경우 마이너 버전을 올려야 합니다. 새로운 필수 키를 도입하거나 형식을 변경하는 경우 반드시 메이저 버전을 올려야 합니다. 그 밖의 경우를 어떻게 처리할지는 각 PEP의 결정에 맡깁니다.

잠금 파일이 명시하는 버전의 메이저 버전은 지원되지만 마이너 버전은 지원되지 않거나 인식되지 않는 경우(예: 설치 프로그램은 "1.0"을 지원하지만 잠금 파일은 "1.1"을 명시하는 경우), 설치 프로그램은 반드시 사용자에게 경고해야 합니다.

잠금 파일이 지원되지 않는 메이저 버전을 명시하는 경우(예: 설치 프로그램은 "1.9"를 지원하지만 잠금 파일은 "2.0"을 명시하는 경우), 설치 프로그램은 반드시 오류를 발생시켜야 합니다.

created-at

이 필드는 필수입니다.

잠금 파일이 생성된 시점의 타임스탬프입니다(TOML의 네이티브 타임스탬프 타입을 사용합니다). 모호함을 피하기 위해 반드시 UTC 시간대로 기록해야 합니다.

SOURCE_DATE_EPOCH 환경 변수가 설정되어 있으면 잠금 도구는 반드시 이 값을 타임스탬프로 사용해야 합니다. 이는 잠금 파일 자체의 재현성을 높여 줍니다.

[tool]

도구는 tool 테이블 아래에 자체 하위 테이블을 만들 수 있습니다. 이 테이블에 대한 규칙은 build system declaration spec에 있는 pyproject.toml과 그 [tool] 테이블에 대한 규칙과 같습니다.

[metadata]

이 테이블은 필수입니다.

잠금 파일 전체에 적용되는 데이터를 담고 있는 테이블입니다.

metadata.marker

dependency specifier spec에 명시된 환경 마커를 담은 문자열을 저장하는 키입니다.

잠금 도구는 잠금 파일이 생성될 때 적용된 제약 사항을 나타내는 환경 마커를 지정할 수 있습니다.

설치 프로그램이 지정된 환경 마커를 만족하지 않는 환경에 설치하는 경우, 잠금 파일이 대상 설치 환경을 지원하지 않으므로 설치 프로그램은 반드시 오류를 발생시켜야 합니다.

metadata.tag

platform compatibility tags (즉, 휠 태그)를 지정하는 문자열을 저장하는 키입니다. 이 태그는 압축된 태그 집합일 수 있습니다.

설치 프로그램이 지정된 태그(집합)를 만족하지 않는 환경에 설치하는 경우, 잠금 파일이 대상 설치 환경을 지원하지 않으므로 설치 프로그램은 반드시 오류를 발생시켜야 합니다.

metadata.requires

이 필드는 필수입니다.

dependency specifier spec을 따르는 문자열의 배열입니다. 이 배열은 잠금 파일의 최상위 패키지 의존성을 나타내며, 따라서 의존성 그래프의 루트가 됩니다.

metadata.requires-python

이 잠금 파일이 지원하는 Python 버전을 지정하는 문자열입니다. core metadata spec에서 Requires-Python 필드에 대해 지정한 것과 동일한 형식을 따릅니다.

[[package._name_._version_]]

이 배열은 필수입니다.

설치할 수 있는 (휠) 파일들의 항목을 담고 있는 패키지 및 버전별 배열입니다(패키지와 버전은 각각 _name_과 _version_으로 표현됩니다).

잠금 도구는 반드시 simple repository API에 따라 프로젝트 이름을 정규화해야 합니다. 설치할 프로젝트의 일부로 extras가 지정된 경우, extras를 키 이름에 포함하고 사전순으로 정렬해야 합니다.

파일 내에서 프로젝트 테이블은 다음 기준에 따라 정렬하는 것이 좋습니다:

  1. 사전순에 따른 프로젝트/키 이름
  2. version specifiers spec에 따라 가장 새로운/높은 버전부터 오래된/낮은 버전 순으로 정렬한 패키지 버전
  3. 사전순에 따른 선택적 의존성(extras)
  4. filename 필드(아래에서 설명)를 기준으로 한 파일 이름

이러한 권장 사항은 도구를 실행할 때마다 생기는 diff 변경을 최소화하기 위한 것입니다.

package._name_._version_.filename

이 필드는 필수입니다.

배열의 항목이 나타내는 파일의 기본 이름(즉, os.path.basename()/pathlib.PurePath.name이 나타내는 것)을 표현하는 문자열입니다. 파일 이름에서 도출되는 wheel 태그를 해석하려면 파일 이름이 필요하므로, 설치 프로그램을 단순화하기 위해 이 필드는 필수입니다. 또한 이 필드는 배열 항목이 어느 파일에 해당하는지가 항상 명확하도록 보장합니다.

[package._name_._version_.hashes]

이 테이블은 필수입니다.

해시 알고리즘을 지정하는 키와, package._name_._version_ 테이블의 이 항목이 나타내는 파일의 해시를 값으로 갖는 테이블입니다.

잠금 도구는 해시를 사전식 순서로 나열해야 합니다(SHOULD). 이는 diff 크기를 최소화하고 해시 값 변경을 간과할 가능성을 줄이기 위함입니다.

설치 프로그램은 반드시 지정된 해시 중 하나와 일치하는 파일만 설치해야 합니다.

package._name_._version_.url

파일을 가져올 URL을 나타내는 문자열입니다.

설치 프로그램은 URL에 대해 원하는 어떤 스킴이든 지원할 수 있습니다. 스킴이 없는 URL은 로컬 파일 경로(잠금 파일에 대한 상대 경로와 절대 경로 모두)로 간주해야 합니다(MUST). 설치 프로그램은 최소한 HTTPS URL과 로컬 파일 경로를 반드시 지원해야 합니다.

지정된 해시와 일치하는 파일을 다른 수단(예: 파일 시스템의 캐시 디렉터리)으로 찾을 수 있는 경우, 설치 프로그램은 파일을 가져오는 데 URL을 사용하지 않기로 선택할 수 있습니다.

package._name_._version_.direct

direct URL origin of installed distributions spec에 명시된 대로 설치 프로그램이 프로젝트를 “직접” 설치된 것으로 간주해야 하는지를 나타내는 불리언입니다.

키가 참이면, 설치 프로그램은 설치를 “직접”으로 기록하기 위해 반드시 direct URL origin of installed distributions spec을 따라야 합니다.

package._name_._version_.requires-python

이 파일이 지원하는 Python 버전을 지정하는 문자열입니다. core metadata spec에서 Requires-Python 필드에 대해 지정한 것과 동일한 형식을 따릅니다.

package._name_._version_.requires

이 파일의 의존성을 나타내는, dependency specifier spec을 따르는 문자열의 배열입니다.

예시

version = "1.0"
created-at = 2021-10-19T22:33:45.520739+00:00

[tool]
# Tool-specific table.

[metadata]
requires = ["mousebender", "coveragepy[toml]"]
marker = "sys_platform == 'linux'"  # As an example for coverage.
requires-python = ">=3.7"

[[package.attrs."21.2.0"]]
filename = "attrs-21.2.0-py2.py3-none-any.whl"
hashes.sha256 = "149e90d6d8ac20db7a955ad60cf0e6881a3f20d37096140088356da6c716b0b1"
url = "https://files.pythonhosted.org/packages/20/a9/ba6f1cd1a1517ff022b35acd6a7e4246371dfab08b8e42b829b6d07913cc/attrs-21.2.0-py2.py3-none-any.whl"
requires-python = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"

[[package.attrs."21.2.0"]]
# If attrs had another wheel file (e.g. that was platform-specific),
# it could be listed here.

[[package."coveragepy[toml]"."6.2.0"]]
filename = "coverage-6.2-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl"
hashes.sha256 = "c7912d1526299cb04c88288e148c6c87c0df600eca76efd99d84396cfe00ef1d"
url = "https://files.pythonhosted.org/packages/da/64/468ca923e837285bd0b0a60bd9a287945d6b68e325705b66b368c07518b1/coverage-6.2-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl"
requires-python = ">=3.6"
requires = ["tomli"]

[[package."coveragepy[toml]"."6.2.0"]]
filename = "coverage-6.2-cp310-cp310-musllinux_1_1_x86_64.whl "
hashes.sha256 = "276651978c94a8c5672ea60a2656e95a3cce2a3f31e9fb2d5ebd4c215d095840"
url = "https://files.pythonhosted.org/packages/17/d6/a29f2cccacf2315150c31d8685b4842a6e7609279939a478725219794355/coverage-6.2-cp310-cp310-musllinux_1_1_x86_64.whl"
requires-python = ">=3.6"
requires = ["tomli"]

# More wheel files for `coverage` could be listed for more
# extensive support (i.e. all Linux-based wheels).

[[package.mousebender."2.0.0"]]
filename = "mousebender-2.0.0-py3-none-any.whl"
hashes.sha256 = "a6f9adfbd17bfb0e6bb5de9a27083e01dfb86ed9c3861e04143d9fd6db373f7c"
url = "https://files.pythonhosted.org/packages/f4/b3/f6fdbff6395e9b77b5619160180489410fb2f42f41272994353e7ecf5bdf/mousebender-2.0.0-py3-none-any.whl"
requires-python = ">=3.6"
requires = ["attrs", "packaging"]

[[package.packaging."20.9"]]
filename = "packaging-20.9-py2.py3-none-any.whl"
hashes.blake-256 = "3e897ea760b4daa42653ece2380531c90f64788d979110a2ab51049d92f408af"
hashes.sha256 = "67714da7f7bc052e064859c05c595155bd1ee9f69f76557e21f051443c20947a"
url = "https://files.pythonhosted.org/packages/3e/89/7ea760b4daa42653ece2380531c90f64788d979110a2ab51049d92f408af/packaging-20.9-py2.py3-none-any.whl"
requires-python = ">=3.6"
requires = ["pyparsing"]

[[package.pyparsing."2.4.7"]]
filename = "pyparsing-2.4.7-py2.py3-none-any.whl"
hashes.sha256 = "ef9d7589ef3c200abe66653d3f1ab1033c3c419ae9b9bdb1240a85b024efc88b"
url = "https://files.pythonhosted.org/packages/8a/bb/488841f56197b13700afd5658fc279a2025a39e22449b7cf29864669b15d/pyparsing-2.4.7-py2.py3-none-any.whl"
direct = true  # For demonstration purposes.
requires-python = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*"

[[package.tomli."2.0.0"]]
filename = "tomli-2.0.0-py3-none-any.whl"
hashes.sha256 = "b5bde28da1fed24b9bd1d4d2b8cba62300bfb4ec9a6187a957e8ddb9434c5224"
url = "https://files.pythonhosted.org/packages/e2/9f/5e1557a57a7282f066351086e78f87289a3446c47b2cb5b8b2f614d8fe99/tomli-2.0.0-py3-none-any.whl"
requires-python = ">=3.7"

잠금 도구에 대한 기대 사항

잠금 도구는 지정된 플랫폼에서 설치 대상이 되는 패키지들을 위상 정렬했을 때, 어떤 패키지든 설치 대상이 되는 버전이 단 하나뿐이고 각 패키지마다 설치할 수 있는 호환 파일이 적어도 하나 존재하는 그래프가 되도록 잠금 파일을 생성해야 합니다(MUST). 이를 통해 지원되는 모든 플랫폼에 대해, 설치 프로그램이 내릴 수 있는 유일한 결정이 설치할 “가장 적합한” wheel이 무엇인지(아래에서 논의합니다)뿐인 잠금 파일이 만들어집니다.

로커는 설치 프로그램에 대해 이 결과를 강제하기 위해 metadata.marker, metadata.tag, metadata.requires-python을 적절히 활용하고, requires를 통해 지정된 환경 마커와 requires-python을 통해 지정된 Python 버전 요구 사항도 활용할 것으로 기대됩니다. 달리 말하면, 잠금 파일에 사용되는 정보는 잠금 도구의 입력을 가공하지 않은 원래 그대로일 필요가 없으며, 오히려 잠금 도구의 목표에 도움이 되도록 필요에 따라 변경되어야 합니다.

설치 프로그램에 대한 기대 사항

무엇을 설치할지 결정하는 데 기대되는 알고리즘은 다음과 같습니다.

  1. metadata.requires를 시작점/루트로 삼아 잠금 파일의 데이터를 바탕으로 의존성 그래프를 구성합니다.
  2. 지정된 플랫폼에서 지원되지 않는 모든 파일을 제거합니다.
  3. requires에 대한 마커 평가를 바탕으로 패키지 사이의 관련 없는 간선을 모두 제거합니다.
  4. 어떤 패키지 버전이 여전히 의존성 그래프의 루트에서 도달 가능하지만 호환되는 파일이 하나도 없으면 오류를 발생시킵니다.
  5. 남은 모든 패키지에 설치할 버전이 하나만 있는지 확인하고, 그렇지 않으면 오류를 발생시킵니다.
  6. 남아 있는 각 패키지에 대해 가장 적합한 wheel 파일을 설치하십시오.

설치 프로그램은 “가장 적합한 wheel 파일”이 무엇인지 결정하기 위해 반드시 결정론적 알고리즘을 따라야 합니다. 이를 위한 간단한 해결책은 wheel 파일 우선순위를 결정하기 위해 packaging 프로젝트와 그 packaging.tags 모듈에 의존하는 것입니다.

설치 프로그램은 빈 환경에 설치하는 것을 반드시 지원해야 합니다. 설치 프로그램은 이미 설치된 패키지가 있는 환경에 설치하는 것(그리고 이를 지원하는 데 수반되는 모든 것)을 지원할 수 있습니다.

(잠재적) 도구 지원

pip 팀은 이 PEP가 수락되면 이를 지원하는 데 관심이 있다고 밝혔습니다. pip에 대한 현재 제안은 pip-tools의 필요성을 대체할 수도 있습니다.

PDM도 이 PEP가 수락되면 지원하겠다고 밝혔습니다.

Pyflow는 이 PEP의 “아이디어가 마음에 든다”고 밝혔습니다.

Poetry는 “Poetry는 지원되지 않는 sdist 파일, 디렉터리, VCS 의존성을 지원한다”는 이유로 이 PEP를 현재 형태로는 지원하지 않겠다고 밝혔습니다. 의존성과 관련하여 일어날 수 있는 일을 더 잘 반영하기 위해 의도적으로 파일 수준에서 요구 사항을 기록하는 방식은 “Poetry의 설계와 모순된다”고 합니다. 또한 “Poetry는 poetry.lock 파일에 있는 정보를 다른 형식으로 내보내며” Poetry.lock 파일에는 sdist와 소스 트리가 포함되므로, 이 PEP의 잠금 파일로 내보내기를 지원하는 것도 배제됩니다. 따라서 Poetry의 잠금 파일을 이 PEP의 잠금 파일 형식으로 깔끔하게 변환할 수 없습니다.

하위 호환성

잠금 파일에 관한 기존 명세가 없으므로, 명시적인 하위 호환성 문제는 없습니다.

자체 잠금 파일을 가진 기존 도구의 경우, 어느 정도의 업데이트가 필요할 것입니다. 대부분은 잠금 파일의 이름은 문서화하지만, 그 내용은 문서화하지 않습니다. 잠금 파일을 버전 관리에 커밋하지 않는 프로젝트는 .gitignore 파일 또는 그에 상응하는 파일을 업데이트해야 할 것입니다. 잠금 파일을 버전 관리에 커밋하는 프로젝트는 어떤 파일을 커밋할지를 변경해야 할 것입니다.

pipenv처럼 잠금 파일 형식을 문서화하는 프로젝트는 잠금 파일 형식을 변경하는 메이저 버전 릴리스가 필요할 가능성이 매우 높습니다.

전환 계획

일반적으로 이 PEP는 다음과 같은 경우에 성공적이라고 볼 수 있습니다.

  1. 기존 도구 두 개가 잠금 도구가 되는 경우(예: pip-tools, PDM, pip freeze를 통한 pip).
  2. Pip이 설치 프로그램이 되었습니다.
  3. Python에 특화되지 않은 주요 플랫폼 하나가 이 파일 형식을 지원하는 경우(예: 클라우드 제공업체).

이는 상호 운용성, 사용성, 그리고 프로그래밍 커뮤니티 및 업계의 수용을 보여 줄 것입니다.

전환 계획 측면에서는 이러한 바람직한 결과로 이어질 수 있는 여러 단계가 있을 수 있습니다. 다음은 이 PEP가 널리 사용되도록 하는 다소 이상적인 계획입니다.

사용성

첫째, 잠금 파일을 생성하는 pip freeze에 상응하는 도구를 개발할 수 있습니다. 설치된 패키지만으로는 잠금 파일을 정적으로 생성하기에 충분한 정보를 얻을 수 없지만, 사용자가 로컬 디렉터리와 색인 URL을 제공하면 잠금 파일을 구성할 수 있습니다. 그러면 잠금 파일이 현재 플랫폼으로 한정되므로 요구 사항 파일보다 더 엄격한 잠금 파일을 얻게 됩니다. 또한 이를 통해 사람들은 자신의 환경이 재현 가능한지 확인할 수 있습니다.

둘째, 독립형 설치 프로그램을 개발해야 합니다. 설치 프로그램에 대한 요구 사항은 pip이 제공하는 것보다 훨씬 단순하므로, 독립적으로 개발된 설치 프로그램을 갖추는 것은 합리적일 것입니다.

셋째, pip-tools가 출력하는 것과 같은 고정된 요구 사항 파일을 변환하는 도구를 개발할 수 있습니다. 위에서 설명한 pip freeze에 상응하는 도구와 마찬가지로, 사용자의 입력이 일부 필요할 수 있습니다. 하지만 이 도구는 적절한 요구 사항 파일을 가진 누구에게나 전환 단계 역할을 할 수 있습니다. 이는 또한 pip-tools에 이 PEP를 사용하기 위한 --lockfile 플래그 같은 것이 추가되기 전에 시험 역할을 할 수도 있습니다.

이 모든 것은 PEP가 조건부 수락에서 완전한 수락으로 전환되기 전에 요구될 수 있습니다(그리고 커뮤니티에 이 PEP가 유용할 수 있는지 시험해 볼 기회를 제공합니다).

상호 운용성

이 시점에서의 목표는 도구 간 상호 운용성을 높이는 것입니다.

첫째, pip이 설치 프로그램이 될 것입니다. 가장 널리 사용되는 설치 프로그램이 이 형식을 지원하게 함으로써, 사람들은 잠금 파일을 실제로 사용하는 데 필요한 도구가 갖추어질 것임을 알고 로커 측면에서 혁신할 수 있습니다.

둘째, pip가 잠금 도구가 됩니다. 이번에도 pip의 영향력 덕분에 대다수 Python 사용자가 매우 빠르게 이 형식을 이용할 수 있게 될 것입니다.

셋째, 기존 잠금 파일 형식을 가진 프로젝트(예: PDM 또는 Pyflow)가 최소한 이 잠금 파일 형식으로 내보내기를 지원합니다. 이는 이 형식이 다른 프로젝트의 요구를 충족한다는 것을 보여 줄 것입니다.

수락

커뮤니티 전반에서 도구를 사용할 수 있게 되면, Python 커뮤니티에만 전적으로 묶여 있지 않은 이들이 자신의 사용자가 원한다고 생각하는 바에 따라 이 파일 형식을 지원함으로써 수락이 드러날 것입니다.

첫째, 코드 편집기처럼 요구 사항 파일을 다루는 도구가 잠금 파일도 동등하게 지원하는 것입니다.

둘째, 클라우드 제공자처럼 요구 사항 파일을 사용하는 쪽에서 잠금 파일도 받아들이는 것입니다.

이 시점에 이르면 PEP는 일반적인 수락 측면에서 요구 사항 파일과 대등할 만큼 충분히 널리 퍼졌을 것이며, 프로젝트들이 이 PEP를 위해 자체 잠금 파일을 버렸다면 그 이상일 수도 있습니다.

보안 영향

잠금 파일은 보안 문제를 일으켜서는 안 되며, 오히려 이를 해결하는 데 도움이 되어야 합니다. 파일의 해시 기록을 요구하므로, 해시 정보가 기록되어 있는 잠금 파일은 코드 변조를 방지하는 데 도움이 될 수 있습니다. wheel 파일에만 의존한다는 것은 어떤 파일이 설치될지 미리 알 수 있고 재현 가능하다는 것을 의미합니다. 잠금 파일은 또한 악의적일 수도 있는 예기치 않은 패키지 업데이트가 설치되는 것을 방지하는 데 도움이 됩니다.

교육 방법

이 PEP의 교육은 일상적으로 사용되는 로커와 설치 프로그램에 크게 좌우될 것입니다. 하지만 개념적으로는 잠금 파일이 프로젝트가 동작하는 데 무엇을 설치해야 하는지를 명시한다고 사용자에게 가르칠 수 있습니다. 사용자가 잠금 파일에 왜 관심을 가져야 하는지 깨달을 수 있도록 일관성과 보안이라는 이점을 강조해야 합니다.

참조 구현

개념 증명(proof-of-concept) 잠금 도구는 https://github.com/frostming/pep665_poc 에서 찾을 수 있습니다. 아직 구현된 설치 프로그램은 없지만, 이 PEP의 설계상 로커가 구현하기 더 어려운 부분임을 시사합니다.

기각된 아이디어

TOML 이외의 파일 형식

JSON도 잠시 고려되었지만, 다음과 같은 이유로:

  1. TOML이 이미 pyproject.toml에 사용되고 있음
  2. TOML이 사람이 읽기에 더 좋음
  3. TOML이 더 나은 diff를 만들어 냄

TOML을 사용하기로 결정했습니다. Python 표준 라이브러리에 TOML 파서가 없다는 우려가 일부 있었지만, 대부분의 패키징 도구가 pyproject.toml 덕분에 이미 TOML 파서를 사용하고 있으므로 이 문제는 결정적인 걸림돌로 보이지 않았습니다. 또한 과거에 일부는 이러한 우려에 반박하며, 패키징 도구가 의존성 설치를 극도로 꺼리고 패키지를 벤더링(vendoring)할 수도 없다고 느낀다면 패키징 생태계에는 서드파티 TOML 파서에 의존해야 하는 문제보다 훨씬 더 큰, 바로잡아야 할 문제가 있는 것이라고 주장했습니다.

대안적인 이름 체계

파일을 설치할 디렉터리를 지정하는 방안도 고려되었지만, 사람들이 이 아이디어를 달가워하지 않아 결국 기각되었습니다.

특별한 파일 이름 접미사를 두지 말자는 제안도 있었지만, 그렇게 하면 도구가 파일을 찾아내기가 너무 어려워진다고 판단했습니다.

단일 잠금 파일 지원

한때 가능한 모든 잠금 정보를 담은 단일 잠금 파일만 지원하자는 아이디어가 고려되었습니다. 하지만 여러 환경을 지원할 수 있는 잠금 파일 형식과 재현 가능한 빌드를 위한 엄격한 잠금 결과를 모두 아우르는 데이터 형식을 고안하려 하면 상당히 복잡하고 번거로워진다는 사실이 금세 분명해졌습니다.

잠금 파일 디렉터리와 함께 pyproject-lock.toml이라는 이름의 단일 잠금 파일도 지원하자는 아이디어도 고려되었습니다. 하지만 단일 잠금 파일인 경우 디렉터리를 생략해서 얻을 수 있는 단순함은 불필요해 보였습니다. 무엇이 pyproject-lock.toml 파일이 되어야 하고 무엇이 pyproject-lock.d에 들어가야 하는지에 대한 적절한 로직을 정의하려는 것은 불필요하게 복잡해 보였습니다.

의존성 그래프 대신 평면 목록 사용

이 PEP의 첫 번째 버전은 잠금 파일에 의존성 그래프라는 개념을 두지 않도록 제안했습니다. 대신, 잠금 파일은 특정 플랫폼에 설치해야 할 것을 정확히 나열하여, 설치 프로그램이 무엇을 설치할지 전혀 결정할 필요 없이 잠금 파일이 대상 플랫폼에서 동작하는지만 검증하면 되도록 하려 했습니다.

이 아이디어는 잠재적인 PEP 508 환경 마커 조합의 수 때문에 결국 기각되었습니다. 프로젝트가 크로스 플랫폼을 지원하려 할 때 잠금 도구가 가능한 모든 조합을 개별 잠금 파일로 생성하도록 하는 것은 지나치다고 결정했습니다.

파일 이름에 휠 태그 사용

metadata.tag 필드를 두는 대신 태그를 파일 이름에 인코딩하자는 제안이 있었습니다. 하지만 metadata.marker 필드가 추가되었고 태그가 필요 없는 경우를 어떻게 처리할지의 문제도 있어 이 아이디어는 폐기되었습니다.

requires의 다른 이름 후보

최종적으로 requires가 된 항목의 다른 이름 후보로는 installs, needs, dependencies가 있었습니다. 처음에 이 PEP는 Python 초보자에게 어떤 용어를 선호하는지 물어본 뒤 needs를 선택했습니다. 그러나 이 PEP의 이전 초안에 대한 피드백을 바탕으로 requires를 용어로 선택했습니다.

PEP 650 수락

PEP 650은 잠금 파일 형식을 표준화하는 대신 설치 프로그램을 위한 API를 명세하는 방식(PEP 517과 같은 방식)으로 이 문제를 해결하려 했던 이전의 시도였습니다. PEP 650에 대한 초기 반응은 온건하거나 미지근했다고 볼 수 있습니다. 사람들은 이 PEP를 구현하기 위해 어떤 도구가 어떤 기능을 제공해야 하는지를 두고 계속 혼란스러워하는 것처럼 보였습니다. 또한 패키징과 관련된 모든 작업을 수행하려면 Python API를 실행해야 하므로 잠재적으로 더 많은 오버헤드가 발생했습니다.

이 PEP는 API 대신 (PEP 621처럼) 산출물을 중심으로 표준화하는 방식을 선택합니다. 이렇게 하면 잠금 파일을 생성하거나 갱신하거나, 심지어 잠금 파일에 나열된 패키지를 설치하는 등의 작업에 굳이 Python을 사용할 필요가 없어지므로 더 많은 도구 통합이 가능해집니다. 또한 의존성 그래프의 세부 사항을 사람이 읽을 수 있는 형식으로 기록하도록 강제하므로 인트로스펙션이 더 쉬워집니다. 또한 사람들이 알아야 할 내용을 더 많이 표준화하므로 지식을 더 쉽게 공유할 수 있습니다(예: 도구가 생성하는 산출물을 이해하는 측면에서 튜토리얼을 도구 간에 더 쉽게 옮겨 적용할 수 있습니다). 게다가 이는 다른 언어 커뮤니티들이 이미 채택했고 만족하는 것으로 보이는 접근 방식이기도 합니다.

이 PEP가 수락되면 PEP 650은 거부됩니다.

파일 단위 대신 패키지 단위로 요구 사항 명세하기

이 PEP의 이전 초안은 의존성을 파일 단위가 아닌 패키지 수준에서 명세했습니다. 이는 전통적으로 패키징 시스템이 동작해 온 방식이지만, 실제로 의존성이 명세되는 방식을 정확히 반영하지는 못했습니다. 따라서 이 PEP는 이후 의존성을 실제로 명세할 수 있는 세분성을 반영하도록 갱신되었습니다.

잠금 도구가 입력을 수집하는 위치 명세하기

이 PEP는 잠금 도구가 입력을 얻는 방법을 명세하지 않습니다. 초기에는 PEP 621을 부분적으로 재사용하자는 제안이 있었지만, 색인 등을 명세하는 측면에서 잠재적 입력이 얼마나 유연해야 하는지를 두고 의견이 갈렸기 때문에 이 문제는 별도의 PEP에 맡기는 것이 가장 좋겠다고 결정했습니다.

소스 배포판과 소스 트리를 선택적으로 지원되는 파일 형식으로 허용하기

광범위한 논의 끝에, 이 PEP는 소스 배포판(sdist)이나 소스 트리를 코드에 허용되는 형식으로 지원하지 않기로 결정했습니다. 이 PEP에 sdist와 소스 트리를 도입하면 sdist나 소스 트리를 빌드하기 위해 코드를 실행해야 하므로 재현성과 보안이라는 목표가 즉시 무너집니다. 또한 sdist와 소스 트리는 동적으로 빌드되는 특성이 있어서, 설치 프로그램은 빌드와 설치 양쪽 관점 모두에서 sdist가 동적으로 생성하는 요구 사항을 무엇이든 완전히 해석해야 하므로, (적어도) 설치 프로그램의 복잡도가 크게 늘어날 것입니다.

이 모든 이유로, sdist와 소스 트리 지원에 관해서는 이 PEP가 수락되거나 거부된 이후에 별도로 논의하는 것이 가장 좋겠다고 결정했습니다. 제안된 파일 형식에는 버전이 매겨지므로, 이후의 PEP에서 sdist와 소스 트리 지원을 도입하는 것이 가능합니다.

다만 이 PEP와 함께 사용할 대역 외(out-of-band) 해결책이 개발되는 것을 이 PEP가 막는 것은 아니라는 점에 유의해야 합니다. 한 가지 방법은 sdist로부터 wheel 파일을 빌드하고, 배포 시 이를 코드와 함께 제공하여 잠금 파일에 포함될 수 있도록 하는 것입니다. 또 다른 방법은 sdist와 소스 트리에 한해서만 requirements 파일을 사용하고, 모든 wheel에 대해서는 잠금 파일에 의존하는 것입니다.

미해결 문제

없습니다.

감사의 글

PEP 508 요구 사항을 설치 시점에 동적으로 해석하는 문제에 관해 의견을 주신 PDM의 Frost Ming과 Poetry의 Sébastien Eustace에게 감사드립니다.

재현 가능한 빌드가 이 PEP의 관심사로 계속 남도록 해 주신 Kushal Das에게 감사드립니다.

처음에 사소한 논쟁(bikeshedding)을 매듭짓고 needs라는 페인트 색을 골라 주신 Andrea McInnes에게 감사드립니다(이를 계기로 사람들은 오히려 requires라는 색으로 뜻을 모았습니다).