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

Python 개선 제안 한국어 번역

PEP 751 – 설치 재현성을 위해 Python 의존성을 기록하는 파일 형식

Author:
Brett Cannon <brett at python.org>
Status:
Final
Type:
Standards Track
Topic:
Packaging
Created:
24-Jul-2024
Post-History:
25-Jul-2024 30-Oct-2024 15-Jan-2025
Replaces:
665
Resolution:
31-Mar-2025

Table of Contents

번역·라이선스 안내

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

Important

This PEP is a historical document. The up-to-date, canonical spec, pylock.toml Specification, is maintained on the PyPA specs page.

×

See the PyPA specification update process for how to propose changes.

초록

이 PEP는 Python 환경에서 재현 가능한 설치가 가능하도록 의존성을 지정하는 새로운 파일 형식을 제안합니다. 이 형식은 사람이 읽을 수 있고 기계가 생성하도록 설계되었습니다. 이 파일을 사용하는 설치 프로그램은 설치 시점에 의존성 해석을 하지 않고도 무엇을 설치할지 계산할 수 있어야 합니다.

동기

현재 가상 환경에 설치해야 할 직접 의존성과 간접 의존성을 지정하는 잠금 파일(lock file)과 같은 불변 기록을 만들기 위한 표준은 없습니다.

커뮤니티에 이 문제에 대해 널리 알려진 해결책이 적어도 다섯 가지(PDM, pip freeze, pip-tools, Poetry, uv) 있다는 점을 고려하면, 잠금 파일에 대한 수요는 전반적으로 있는 것으로 보입니다.

이 도구들은 지원하는 잠금 시나리오도 서로 다릅니다. 예를 들어, pip freeze와 pip-tools는 현재 환경에 대한 일회용 잠금 파일만 생성하는 반면, PDM, Poetry, uv는 여러 환경과 사용 사례를 한 번에 잠글 수 있거나 잠그려고 시도합니다. 또한 일부 도구가 공급망 공격에 대비한 안전한 기본값을 갖추지 못했다는 우려도 있습니다(예: 파일 해시 포함).

표준이 없다는 점에는 몇 가지 단점도 있습니다. 예를 들어, 잠금 파일을 다루려는 모든 도구는 어떤 형식을 지원할지 선택해야 하므로 일부 사용자가 지원받지 못할 수 있습니다(예: Dependabot은 일부 도구만 지원하며, 사용자를 대신해 의존성을 설치해 주는 클라우드 제공업체 등도 마찬가지입니다). 또한 도구 간 이식성에도 영향을 미쳐 벤더 잠금(vendor lock-in)을 초래합니다. 호환성과 상호 운용성이 없으면 잠금 파일을 둘러싼 도구 생태계가 분열되어 사용자와 도구 모두 어떤 잠금 파일 형식을 사용할지 미리 선택해야 하고, 그 결과 다른 형식을 사용하거나 다른 형식으로 전환하는 데 큰 비용이 듭니다(예: 잠금 파일 감사 도구). 하나의 형식으로 뜻을 모으면 이러한 비용과 장벽이 사라집니다.

커뮤니티에서 표준에 가장 가까운 것은 pip의 requirements files로, 앞서 언급한 모든 도구가 이를 파일 형식으로 직접 사용하거나 이 형식으로 내보냅니다(즉, requirements.txt). 안타깝게도 이 형식은 표준이 아니라 관례에 따라 지원됩니다. 또한 pip의 요구에 크게 맞춰 설계되어 유연성과 사용 편의성이 제한됩니다(예: 독자적인 파일 형식입니다). 마지막으로, 이 형식은 기본적으로 안전하지 않습니다(예: 파일 해시 지원은 전적으로 사용자가 직접 켜야 하는 선택 기능이며, requirements 파일에 있는 것 외의 다른 의존성을 찾지 않도록 pip에 따로 지시해야 합니다).

Note

PEP 665의 동기 중 상당 부분이 이 PEP에도 적용됩니다.

근거

이 PEP가 제안하는 파일 형식은 사람이 읽을 수 있도록 설계되었습니다. 이는 사람이 파일 내용을 감사하여 원치 않는 의존성이 잠금 파일에 포함되지 않았는지 확인할 수 있도록 하기 위함입니다.

또한 이 파일 형식은 설치 시점에 해석기(resolver)가 필요하지 않도록 설계되었습니다. 이를 통해 잠금 파일을 사용할 때 무엇이 설치될지 추론하는 일이 크게 단순해집니다. 또한 잠금 파일 생성보다 훨씬 자주 일어나는 설치도 더 빨라질 것입니다.

파일의 데이터는 Python으로 작성되지 않은 도구에서도 사용할 수 있어야 합니다. 이를 통해 예를 들어 클라우드 호스팅 제공업체가 선호하는 프로그래밍 언어로 설치를 수행하는 자체 도구를 작성할 수 있습니다. 이를 통해 잠금 파일을 작성하는 로커(locker)와 잠금 파일로부터 설치하는 설치 프로그램(installer)이라는 개념이 도입됩니다(둘은 같은 도구일 수 있습니다).

파일 형식은 좋은 보안 기본값을 장려해야 합니다. 이 형식은 사람이 직접 작성하도록 의도된 것이 아니므로, 도구가 보안 관련 세부 정보를 제공하도록 하는 것은 합리적이며 큰 부담이 되지 않습니다.

잠금 파일로 사용될 때(예: pip-tools와 pip freeze가 출력하는 것) 잠금 파일의 내용은 requirements files의 사용 사례 대부분을 대체할 수 있어야 합니다. 즉, 이 PEP가 명세하는 파일 형식은 최소한 자체 내부 잠금 파일 형식을 가진 도구의 내보내기 대상으로 사용될 수 있습니다.

잠금 파일은 단일 용도일 수도 있고 다중 용도일 수도 있습니다. 단일 용도 잠금 파일은 requirements.txt 파일처럼 하나의 사용 사례/목적에 쓰이는 파일입니다(그래서 프로젝트가 사용 사례마다 하나씩 여러 개의 requirements 파일을 두는 일이 드물지 않습니다). 다중 용도 잠금 파일은 하나의 파일 안에서 여러 사용 사례를 나타내며, 이는 흔히 extras와 Dependency Groups를 통해 표현됩니다. 따라서 이 PEP는 extras와 의존성 그룹을 적절히 지정할 수 있도록 Environment Markers에 대한 추가 사항을 지원합니다. 이를 통해 하나의 잠금 파일로 그러한 경우들을 지원할 수 있습니다. 이는 잠재적인 잠금 파일의 수를 줄여 줄 뿐만 아니라, 하나의 패키지가 모든 사용 사례에 걸쳐 일관되어야 할 때 이를 더 쉽게 만들어 줍니다(단일 용도 잠금 파일이 여러 개라면 여러 잠금 파일 사이를 조율해야 합니다). 또한 이 지원은 이 PEP가 pyproject.toml 파일에 기록될 수 있는 패키지 설치 관련 데이터를 모두 지원한다는 것을 의미합니다. 이 지원을 통해 일부 도구가 내부 잠금 파일을 완전히 없애고 이 PEP가 명세하는 내용에만 의존할 수 있게 되기를 기대합니다.

명세

파일 이름

잠금 파일의 이름은 반드시(MUST) pylock.toml이어야 하며, 잠금 파일에 이름을 붙이고 싶거나 잠금 파일이 여러 개 있는 경우에는 정규 표현식 r"^pylock\.([^.]+)\.toml$"과 일치해야 합니다. .toml 파일 확장자를 사용하는 것은 편집기에서 문법 강조를 더 쉽게 하고, 이 파일 형식이 사람이 읽을 수 있도록 의도되었다는 사실을 강조하기 위함입니다. 이름이 붙은 파일의 접두사와 접미사는 쉽게 탐지하고 제거할 수 있도록 가능하면 반드시(MUST) 소문자여야 합니다. 예를 들면 다음과 같습니다.

if len(filename) > 11 and filename.startswith("pylock.") and filename.endswith(".toml"):
    name = filename.removeprefix("pylock.").removesuffix(".toml")

잠금 파일로부터 자동으로 설치하는 서비스는 다음을 찾을 것으로 예상됩니다.

  1. 서비스 이름이 붙은 잠금 파일과 그 파일의 기본 설치
  2. 서비스 이름과 같은 이름의 의존성 그룹이 있는 다중 용도 pylock.toml
  3. pylock.toml의 기본 설치

예를 들어 “spam”이라는 이름의 클라우드 호스트 서비스는 먼저 설치에 사용할 pylock.spam.toml을 찾고, 그 파일이 존재하지 않으면 pylock.toml로부터 설치하면서 “spam”이라는 이름의 의존성 그룹이 있으면 그것을 사용합니다.

잠금 파일은 잠금 파일의 범위에 맞는 디렉터리에 위치해야 합니다(SHOULD). 예를 들어 하나의 pyproject.toml에 대해 잠금을 수행하는 경우 pylock.toml을 같은 디렉터리에 둡니다. 잠금 파일이 모노레포 안의 여러 프로젝트를 다루는 경우, pylock.toml 파일은 잠금 대상이 되는 모든 프로젝트를 포함하는 디렉터리에 위치할 것으로 예상됩니다.

파일 형식

파일 형식은 TOML입니다.

도구는 diff 출력의 노이즈를 최소화하도록 일관된 방식으로 잠금 파일을 작성해야 합니다(SHOULD). 최상위 테이블을 포함하여 테이블의 키는 일관된 순서로 기록되어야 합니다(SHOULD)(참고할 것이 필요하다면, 이 PEP는 키를 논리적인 순서로 적으려고 노력했습니다). 또한 도구는 배열을 일관된 순서로 정렬해야 합니다(SHOULD). 인라인 테이블의 사용 역시 일관되게 유지되어야 합니다(SHOULD).

lock-version

  • 타입: 문자열; 값은 "1.0"
  • 필수 여부: 예
  • 영감: Metadata-Version
  • 파일이 준수하는 파일 형식 버전을 기록합니다.
  • 이 PEP는 초기 버전을 "1.0"으로 지정하며, 향후 표준 업데이트로 변경되기 전까지는 이 값만 유효합니다.
  • 도구가 메이저 버전은 지원하지만 마이너 버전은 지원하지 않는 경우, 도구는 알 수 없는 키를 발견하면 경고해야 합니다(SHOULD).
  • 도구가 메이저 버전을 지원하지 않는 경우, 반드시 오류를 발생시켜야 합니다(MUST).

environments

  • 타입: 문자열 배열
  • 필수 여부: 아니요
  • 영감을 받은 곳: uv
  • 잠금 파일이 호환된다고 간주되는 Environment Markers 목록입니다.
  • 이해하기 쉽도록 도구는 상호 배타적인, 즉 서로 겹치지 않는 환경 마커를 작성해야 합니다(SHOULD).

requires-python

  • 타입: 문자열
  • 필수 여부: 아니요
  • 영감을 받은 곳: PDM, Poetry, uv
  • 잠금 파일이 지원하는 모든 환경과 호환되는 최소 Python 버전(즉, 잠금 파일에 사용할 수 있는 최소 Python 버전)에 대한 Requires-Python을 지정합니다.

extras

  • 타입: 문자열 배열
  • 필수 여부: 아니요. 기본값은 []입니다.
  • 영감: Provides-Extra (multiple use)
  • 이 잠금 파일이 지원하는 extras 목록입니다.
  • 잠금 파일 생성 도구는 extras와 의존성 그룹을 지원하는 잠금 파일의 작성을 지원하지 않기로 선택할 수 있습니다(MAY). 즉, 도구는 일회용 잠금 파일 내보내기만 지원할 수도 있습니다.
  • extras를 지원하는 도구는 의존성 그룹도 지원해야 합니다(MUST).
  • 도구는 잠금 파일 생성에 사용된 입력에 extras가 없었음(예: pyproject.toml 파일에 [project.optional-dependencies] 테이블이 없었던 경우)을 알리기 위해 이 키를 명시적으로 빈 배열로 설정해야 합니다. 이렇게 하면 잠금 파일이 겉보기에는 일회용으로 보이더라도 사실상 다회용임을 알릴 수 있습니다.

dependency-groups

  • 타입: 문자열 배열
  • 필수 여부: 아니요. 기본값은 []입니다.
  • 영감을 받은 곳: Arbitrary tool configuration: the [tool] table
  • 이 잠금 파일이 공개적으로 지원하는 Dependency Groups 목록입니다(즉, 사용자가 도구의 UI를 통해 지정할 수 있을 것으로 예상되는 의존성 그룹입니다).
  • 잠금 파일 생성 도구는 extras와 의존성 그룹을 지원하는 잠금 파일의 작성을 지원하지 않기로 선택할 수 있습니다(MAY). 즉, 도구는 일회용 잠금 파일 내보내기만 지원할 수도 있습니다.
  • 의존성 그룹을 지원하는 도구는 extras도 지원해야 합니다(MUST).
  • 도구는 잠금 파일 생성에 사용된 입력에 의존성 그룹이 없었음(예: pyproject.toml 파일에 [dependency-groups] 테이블이 없었던 경우)을 알리기 위해 이 키를 명시적으로 빈 배열로 설정해야 합니다(SHOULD). 이렇게 하면 잠금 파일이 겉보기에는 일회용으로 보이더라도 사실상 다회용임을 알릴 수 있습니다.

default-groups

  • 타입: 문자열 배열
  • 필수 여부: 아니요. 기본값은 []입니다
  • 영감을 받은 곳: Poetry, PDM
  • 기본적으로 설치되어야 하는 것(예: project.dependencies가 암묵적으로 나타내는 것)을 나타내기 위한 합성 의존성 그룹의 이름입니다.
  • packages.marker 때문에 그러한 그룹이 존재해야 하는 상황에서 사용하기 위한 것입니다.
  • 이 키에 나열된 그룹은 dependency-groups에 나열되지 않아야 합니다(SHOULD NOT). 이 그룹들은 이름을 통해 사용자에게 직접 노출되는 것이 아니라 설치 프로그램의 UI를 통해 노출되도록 의도되었기 때문입니다.

created-by

  • 타입: 문자열
  • 필수 여부: 예
  • 영감을 받은 곳: 잠금 파일 이름에 자신의 이름이 들어가는 도구들
  • 잠금 파일을 생성하는 데 사용된 도구의 이름을 기록합니다.
  • 도구는 [tool] 테이블을 사용하여 잠금 파일을 생성하는 데 어떤 입력이 사용되었는지 추론할 수 있을 만큼 충분한 세부 정보를 기록할 수 있습니다(MAY).
  • 도구가 Python 패키지로 제공되는 경우, 도구를 쉽게 찾을 수 있도록 도구의 정규화된 이름을 기록해야 합니다(SHOULD).

[[packages]]

  • 타입: 테이블 배열
  • 필수 여부: 예
  • 영감을 받은 곳: PDM, Poetry, uv
  • 설치될 수 있는 모든 패키지를 담은 배열입니다.
  • 패키지는 서로 다른 데이터로 여러 번 나열될 수 있지만(MAY), 설치할 모든 패키지는 설치 시점에 단 하나의 항목으로 좁혀져야 합니다(MUST).
packages.name
  • 타입: 문자열
  • 필수 여부: 예
  • 영감을 받은 곳: Name
  • 정규화된 패키지 이름입니다.
packages.version
  • 타입: 문자열
  • 필수 여부: 아니요
  • 영감을 받은 곳: Version
  • 패키지의 버전입니다.
  • 버전이 안정적인 것으로 알려진 경우(즉, sdist 또는 wheels가 지정된 경우) 버전을 지정해야 합니다(SHOULD).
  • 버전이 사용된 코드와 일치한다고 보장할 수 없는 경우(즉, 소스 트리가 사용된 경우) 버전을 포함해서는 안 됩니다(MUST NOT).
packages.marker
  • 타입: 문자열
  • 필수 여부: 아니요
  • 참고: PDM
  • 패키지를 설치해야 하는 조건을 지정하는 환경 마커입니다.
packages.requires-python
[[packages.dependencies]]
  • 타입: 테이블 배열
  • 필수 여부: 아니요
  • 영감을 받은 곳: PDM, Poetry, uv
  • [[packages]]의 다른 항목 중 이 패키지의 직접 의존성인 항목들을 기록합니다.
  • 각 항목은 테이블이며, 대응하는 다른 패키지 항목이 무엇인지 알아내는 데 필요한 최소한의 정보를 담습니다. 이 정보는 키별로 비교했을 때 해당 패키지를 모호함 없이 찾을 수 있을 만큼이어야 합니다(예를 들어 spam 패키지 항목이 두 개 있다면 {name = "spam", version = "1.0.0"}처럼 버전 번호를 넣거나 {name = "spam", vcs = { url = "..."}처럼 출처를 넣을 수 있습니다).
  • 도구는 설치할 때 이 정보를 사용해서는 안 됩니다(MUST NOT). 이 정보는 순전히 감사 목적의 참고용입니다.
[packages.vcs]
  • 타입: 테이블
  • 필수 여부: 아니요. packages.directory, packages.archive, packages.sdist, packages.wheels와 상호 배타적입니다
  • 참고: Direct URL Data Structure
  • 이 항목에 포함된 소스 트리의 버전 관리 시스템 세부 정보를 기록합니다.
  • 도구는 잠금 측면이나 설치 측면에서, 또는 두 측면 모두에서 버전 관리 시스템을 지원하지 않기로 선택할 수 있습니다(MAY).
  • 도구는 사용 가능한 VCS 유형 중 일부만 지원하기로 선택할 수 있습니다(MAY).
  • 도구는 사용자가 버전 관리 시스템 사용 여부를 선택할 수 있는 방법을 제공해야 합니다(SHOULD).
  • 버전 관리 시스템에서 설치하는 것은 직접 URL 참조에서 비롯된 설치로 간주합니다.
packages.vcs.type
  • 타입: 문자열. 지원되는 값은 Registered VCS에 명시되어 있습니다
  • 필수 여부: 예
  • 참고: VCS URLs
  • 사용하는 버전 관리 시스템의 유형입니다.
packages.vcs.url
  • 타입: 문자열
  • 필수 여부: path가 지정되지 않은 경우
  • 참고: VCS URLs
  • 소스 트리의 URL입니다.
packages.vcs.path
  • 타입: 문자열
  • 필수 여부: url이 지정되지 않은 경우
  • 참고: VCS URLs
  • 소스 트리가 있는 로컬 디렉터리의 경로입니다.
  • 상대 경로를 사용하는 경우 반드시(MUST) 이 파일의 위치를 기준으로 한 상대 경로여야 합니다.
  • 경로가 상대 경로인 경우 이식성을 위해 POSIX 스타일 경로 구분자를 명시적으로 사용할 수 있습니다(MAY).
packages.vcs.requested-revision
  • 타입: 문자열
  • 필수 여부: 아니요
  • 참고: VCS URLs
  • 사용자가 요청한 브랜치/태그/ref/커밋/리비전 등입니다.
  • 이 값은 순전히 정보 제공용이며 Direct URL Data Structure를 작성하기 쉽게 하기 위한 것입니다. 저장소를 체크아웃하는 데 사용해서는 안 됩니다(MUST NOT).
packages.vcs.commit-id
  • 타입: 문자열
  • 필수 여부: 예
  • 참고: VCS URLs
  • 설치할 정확한 커밋/리비전 번호입니다.
  • VCS가 커밋 해시 기반 리비전 식별자를 지원하는 경우, 변경할 수 없는 버전의 소스 코드를 참조하도록 반드시(MUST) 해당 커밋 해시를 커밋 ID로 사용해야 합니다.
packages.vcs.subdirectory
  • 타입: 문자열
  • 필수 여부: 아니요
  • 참고: Projects in subdirectories
  • 프로젝트의 루트가 위치한 소스 트리 안의 하위 디렉터리입니다(예: pyproject.toml 파일의 위치).
  • 경로는 반드시(MUST) 소스 트리 구조의 루트를 기준으로 한 상대 경로여야 합니다.
[packages.directory]
  • 타입: 테이블
  • 필수 여부: 아니요. packages.vcs, packages.archive, packages.sdist, packages.wheels와 상호 배타적입니다.
  • 영감: Local directories
  • 해당 디렉터리에 포함된 소스 트리의 로컬 디렉터리 세부 정보를 기록합니다.
  • 도구는 잠금 관점과 설치 관점 중 한쪽 또는 양쪽 모두에서 로컬 디렉터리를 지원하지 않기로 선택할 수 있습니다(MAY).
  • 도구는 사용자가 로컬 디렉터리 사용 여부를 선택할 수 있는 방법을 제공해야 합니다(SHOULD).
  • 디렉터리에서 설치하는 것은 직접 URL 참조에서 비롯된 설치로 간주합니다.
packages.directory.path
  • 타입: 문자열
  • 필수 여부: 예
  • 영감: Local directories
  • 소스 트리가 위치한 로컬 디렉터리입니다.
  • 경로가 상대 경로라면 반드시 잠금 파일의 위치를 기준으로 하는 상대 경로여야 합니다(MUST).
  • 경로가 상대 경로라면 이식성을 위해 POSIX 스타일 경로 구분자를 사용할 수 있습니다(MAY).
packages.directory.editable
  • 타입: 불리언
  • 필수 여부: 아니요. 기본값은 false입니다
  • 영감: Local directories
  • 잠금 시점에 소스 트리가 편집 가능 설치(editable install)였는지를 나타내는 플래그입니다.
  • 사용자의 동작이나 맥락상 편집 가능 설치가 불필요하거나 바람직하지 않은 경우(예: 개발 목적으로 마운트되지 않고 프로덕션에 배포되어 읽기 전용으로 취급될 컨테이너 이미지), 설치 프로그램은 이 플래그를 무시할 수 있습니다(MAY).
packages.directory.subdirectory

packages.vcs.subdirectory를 참조하십시오.

[packages.archive]
  • 타입: 테이블
  • 필수 여부: 아니요
  • 영감: Archive URLs
  • 설치할 아카이브 파일에 대한 직접 참조입니다(휠과 sdist뿐만 아니라 소스 트리를 담은 다른 아카이브 형식도 포함될 수 있습니다).
  • 도구는 잠금 관점과 설치 관점 중 한쪽 또는 양쪽 모두에서 아카이브 파일을 지원하지 않기로 선택할 수 있습니다(MAY).
  • 도구는 사용자가 아카이브 파일 사용 여부를 선택할 수 있는 방법을 제공해야 합니다(SHOULD).
  • 아카이브 파일에서 설치하는 것은 직접 URL 참조에서 비롯된 설치로 간주합니다.
packages.archive.url

packages.vcs.url을 참조하십시오.

packages.archive.path

packages.vcs.path를 참조하십시오.

packages.archive.size
  • 타입: 정수
  • 필수 여부: 아니요
  • 참고: uv, Simple repository API
  • 아카이브 파일의 크기입니다.
  • 도구는 합리적으로 가능한 경우 파일 크기를 제공해야 합니다(SHOULD)(예: HEAD HTTP 요청의 Content-Length 헤더로 파일 크기를 알 수 있는 경우).
packages.archive.upload-time
  • 타입: datetime
  • 필수 여부: 아니요
  • 참고: Simple repository API
  • 파일이 업로드된 시각입니다.
  • 날짜와 시각은 반드시 UTC로 기록해야 합니다(MUST).
[packages.archive.hashes]
  • 타입: 문자열 테이블
  • 필수 여부: 예
  • 참고: PDM, Poetry, uv, Simple repository API
  • 파일의 알려진 해시 값을 나열하는 테이블로, 키는 해시 알고리즘이고 값은 해시 값입니다.
  • 테이블에는 반드시 하나 이상의 항목이 있어야 합니다(MUST).
  • 해시 알고리즘 키는 소문자여야 합니다(SHOULD).
  • hashlib.algorithms_guaranteed에 있는 안전한 알고리즘을 항상 하나 이상 포함해야 합니다(SHOULD)(이 글을 쓰는 시점에는 특히 sha256을 권장합니다).
packages.archive.subdirectory

packages.vcs.subdirectory를 참고하십시오.

packages.index
  • 타입: 문자열
  • 필수 여부: 아니요
  • 영감을 받은 곳: uv
  • sdist나 휠 또는 둘 다를 찾은 Simple repository API 패키지 색인의 기본 URL입니다(예: https://pypi.org/simple/).
  • 가능하면 이 값을 지정하여 software bill of materials (SBOM이라고도 합니다) 생성을 돕고, URL이 더 이상 유효하지 않을 때 파일을 찾는 데 도움이 되도록 해야 합니다(SHOULD).
  • 특정 파일에 기록된 URL이 더 이상 유효하지 않으면(예: HTTP 404 오류 코드를 반환하는 경우) 도구는 색인에서 설치하는 기능을 지원할 수 있습니다(MAY).
[packages.sdist]
  • 타입: 테이블
  • 필수 여부: 아니요. packages.vcs, packages.directory, packages.archive와 상호 배타적입니다.
  • 영감을 받은 곳: uv
  • 패키지의 Source distribution file name에 대한 세부 정보입니다.
  • 도구는 잠금 관점과 설치 관점 중 한쪽 또는 양쪽 모두에서 sdist 파일을 지원하지 않기로 선택할 수 있습니다(MAY).
  • 도구는 사용자가 sdist 파일 사용 여부를 선택할 수 있는 방법을 제공해야 합니다(SHOULD).
packages.sdist.name
  • 타입: 문자열
  • 필수 여부: 아니요. path/ url의 마지막 구성 요소가 같은 값일 경우에는 필수가 아닙니다
  • 영감을 받은 곳: PDM, Poetry, uv
  • Source distribution file name 파일의 파일 이름입니다.
packages.sdist.upload-time

packages.archive.upload-time을 참조하십시오.

packages.sdist.url

packages.archive.url을 참조하십시오.

packages.sdist.path

packages.archive.path를 참조하십시오.

packages.sdist.size

packages.archive.size를 참조하십시오.

packages.sdist.hashes

packages.archive.hashes를 참조하십시오.

[[packages.wheels]]
  • 타입: 테이블 배열
  • 필수 여부: 아니요. packages.vcs, packages.directory, packages.archive와 상호 배타적입니다
  • 영감을 받은 곳: PDM, Poetry, uv
  • 패키지에 대해 Binary distribution format에 명시된 wheel 파일을 기록하기 위한 것입니다.
  • 도구는 잠금과 설치 양쪽 관점 모두에서 반드시 wheel 파일을 지원해야 합니다(MUST).
packages.wheels.name
  • 타입: 문자열
  • 필수 여부: 아니요. path/ url의 마지막 구성 요소가 같은 값일 경우에는 필수가 아닙니다
  • 영감을 받은 곳: PDM, Poetry, uv
  • Binary distribution format 파일의 파일 이름입니다.
packages.wheels.upload-time

packages.archive.upload-time을 참조하십시오.

packages.wheels.url

packages.archive.url을 참조하십시오.

packages.wheels.path

packages.archive.path를 참조하십시오.

packages.wheels.size

packages.archive.size를 참조하십시오.

packages.wheels.hashes

packages.archive.hashes를 참조하십시오.

[[packages.attestation-identities]]
  • 타입: 테이블 배열
  • 필수 여부: 아니요
  • 참고: Provenance objects
  • 이 패키지에 기록된 모든 파일에 대한 증명(attestation)의 기록입니다.
  • 가능한 경우, 도구는 발견된 증명 신원을 포함해야 합니다.
  • 게시자별 키는 Index hosted attestations의 명세에 따라 테이블에 있는 그대로(즉, 최상위 수준에) 포함되어야 합니다.
packages.attestation-identities.kind
  • 타입: 문자열
  • 필수 여부: 예
  • 참고: Provenance objects
  • 신뢰할 수 있는 게시자(Trusted Publisher)의 고유 신원입니다.
[packages.tool]
  • 타입: 테이블
  • 필수 여부: 아니요
  • 영감을 받은 곳: Arbitrary tool configuration: the [tool] table
  • pyproject.toml specification의 [tool] 테이블과 용도가 비슷하지만, 잠금 파일 수준(이 수준은 [tool]을 통해서도 사용할 수 있습니다)이 아닌 패키지 버전 수준에서 사용됩니다.
  • 테이블에 기록된 데이터는 폐기해도 무방한 것이어야 합니다(즉, 설치에 영향을 주어서는 안 됩니다).

[tool]

마커 표현식 문법에 추가된 사항

이 PEP는 [[packages]]의 항목에 대한 추가 기능(extras) 및 의존성 그룹 관계를 packages.marker에 표현할 수 있도록 Environment Markers 명세에 내용을 추가할 것을 제안합니다. 이 PEP에서 설명하는 추가 사항은 오직(ONLY) 이 PEP에서 정의한 잠금 파일의 맥락에서만 적용되며, 마커 문법이 사용되는 다른 맥락(예: METADATA, pyproject.toml)에는 적용되지 않습니다.

첫째, 두 개의 새로운 마커 extras와 dependency_groups를 도입합니다. 이 마커들은 각각 설치가 요청된 추가 기능과 의존성 그룹을 나타냅니다:

diff --git a/source/specifications/dependency-specifiers.rst b/source/specifications/dependency-specifiers.rst
index 06897da2..c9ab247f 100644
--- a/source/specifications/dependency-specifiers.rst
+++ b/source/specifications/dependency-specifiers.rst
@@ -87,7 +87,7 @@ environments::
                      'platform_system' | 'platform_version' |
                      'platform_machine' | 'platform_python_implementation' |
                      'implementation_name' | 'implementation_version' |
-                     'extra' # ONLY when defined by a containing layer
+                     'extra' | 'extras' | 'dependency_groups' # ONLY when defined by a containing layer
                      )
    marker_var    = wsp* (env_var | python_str)
    marker_expr   = marker_var marker_op marker_var

이는 다른 맥락에서 같은 문법 파서를 사용하는 것을 배제하지 않으며(NOT), 다만 새 마커가 맥락에 따라 유효한 것으로 간주되는 경우에만 해당됩니다.

둘째, 마커 명세를 변경하여 (현재 지원하는 문자열과 버전에 더해) 값으로 집합을 허용합니다. 오직 이 PEP에서 도입하는 새 마커만 값으로 집합을 허용합니다(기본값은 빈 집합입니다). 다만 이 변경은 집합 리터럴을 허용하도록 명세를 갱신하는 것은 아닙니다.

셋째, 마커 표현식 문법 명세가 집합을 포함하는 연산을 허용하도록 갱신됩니다:

diff --git a/source/specifications/dependency-specifiers.rst b/source/specifications/dependency-specifiers.rst
index 06897da2..ac29d796 100644
--- a/source/specifications/dependency-specifiers.rst
+++ b/source/specifications/dependency-specifiers.rst
@@ -196,15 +196,16 @@ safely evaluate it without running arbitrary code that could become a security
vulnerability. Markers were first standardised in :pep:`345`. This document
fixes some issues that were observed in the design described in :pep:`426`.

-Comparisons in marker expressions are typed by the comparison operator.  The
-<marker_op> operators that are not in <version_cmp> perform the same as they
-do for strings in Python. The <version_cmp> operators use the version comparison
-rules of the :ref:`Version specifier specification <version-specifiers>`
-when those are defined (that is when both sides have a valid
-version specifier). If there is no defined behaviour of this specification
-and the operator exists in Python, then the operator falls back to
-the Python behaviour. Otherwise an error should be raised. e.g. the following
-will result in  errors::
+Comparisons in marker expressions are typed by the comparison operator and the
+type of the marker value. The <marker_op> operators that are not in
+<version_cmp> perform the same as they do for strings or sets in Python based on
+whether the marker value is a string or set itself. The <version_cmp> operators
+use the version comparison rules of the
+:ref:`Version specifier specification <version-specifiers>` when those are
+defined (that is when both sides have a valid version specifier). If there is no
+defined behaviour of this specification and the operator exists in Python, then
+the operator falls back to the Python behaviour for the types involved.
+Otherwise an error should be raised. e.g. the following will result in errors::

    "dog" ~= "fred"
    python_version ~= "surprise"

넷째, 잠금 파일 밖에서 extras와 dependency_groups를 사용하는 것은 오류로 간주합니다(Core metadata specifications 밖에서 extra를 사용하는 것과 마찬가지입니다).

diff --git a/source/specifications/dependency-specifiers.rst b/source/specifications/dependency-specifiers.rst
index 06897da2..2914ef66 100644
--- a/source/specifications/dependency-specifiers.rst
+++ b/source/specifications/dependency-specifiers.rst
@@ -235,6 +235,11 @@ no current specification for this. Regardless, outside of a context where this
special handling is taking place, the "extra" variable should result in an
error like all other unknown variables.

+The "extras" and "dependency_groups" variables are also special. They are used
+to specify any requested extras or dependency groups when installing from a lock
+file. Outside of the context of lock files, these two variables should result in
+an error like all other unknown variables.
+
.. list-table::
    :header-rows: 1

이러한 변경 사항은 packages.extras/ packages.dependency-groups 및 마커 표현식의 불리언 논리 지원과 함께, 요청된(또는 요청되지 않은) 추가 기능과 의존성 그룹에 따라 패키지를 언제 설치해야 하는지에 대한 임의의 요구 사항을 빠짐없이 표현할 수 있게 합니다. 예를 들어 잠금 파일에 extras = ["extra-1", "extra-2"]가 있다면, 다음과 같은 경우에 패키지를 설치해야 하는지 지정할 수 있습니다:

  • 추가 기능 중 하나라도 지정된 경우 ('extra-1' in extras or 'extra-2' in extras)
  • “extra-1”만 지정된 경우 ('extra-1' in extras and 'extra-2' not in extras)
  • 추가 기능이 하나도 지정되지 않은 경우 ('extra-1' not in extras and 'extra-2' not in extras)

(이 목록이 가능한 모든 불리언 논리 표현식을 망라하는 것은 아닙니다.)

의존성 그룹에도 같은 유연성이 적용됩니다.

사용자가 설치하려는 추가 기능 및/또는 의존성 그룹을 도구에 어떻게 알려 주는지는 도구가 정합니다. 설치 프로그램은 이 PEP에서 제안한 마커 표현식 문법 추가 사항을 반드시 지원해야 합니다(MUST). 로커는 제안된 마커 표현식 문법 추가 사항을 활용하는 잠금 파일의 작성을 지원할 수 있습니다(MAY)(즉, 로커는 일회용 잠금 파일의 작성만 지원하도록 선택할 수 있습니다).

예시

lock-version = '1.0'
environments = ["sys_platform == 'win32'", "sys_platform == 'linux'"]
requires-python = '==3.12'
created-by = 'mousebender'

[[packages]]
name = 'attrs'
version = '25.1.0'
requires-python = '>=3.8'
wheels = [
  {name = 'attrs-25.1.0-py3-none-any.whl', upload-time = 2025-01-25T11:30:10.164985+00:00, url = 'https://files.pythonhosted.org/packages/fc/30/d4986a882011f9df997a55e6becd864812ccfcd821d64aac8570ee39f719/attrs-25.1.0-py3-none-any.whl', size = 63152, hashes = {sha256 = 'c75a69e28a550a7e93789579c22aa26b0f5b83b75dc4e08fe092980051e1090a'}},
]
[[packages.attestation-identities]]
environment = 'release-pypi'
kind = 'GitHub'
repository = 'python-attrs/attrs'
workflow = 'pypi-package.yml'

[[packages]]
name = 'cattrs'
version = '24.1.2'
requires-python = '>=3.8'
dependencies = [
    {name = 'attrs'},
]
wheels = [
  {name = 'cattrs-24.1.2-py3-none-any.whl', upload-time = 2024-09-22T14:58:34.812643+00:00, url = 'https://files.pythonhosted.org/packages/c8/d5/867e75361fc45f6de75fe277dd085627a9db5ebb511a87f27dc1396b5351/cattrs-24.1.2-py3-none-any.whl', size = 66446, hashes = {sha256 = '67c7495b760168d931a10233f979b28dc04daf853b30752246f4f8471c6d68d0'}},
]

[[packages]]
name = 'numpy'
version = '2.2.3'
requires-python = '>=3.10'
wheels = [
  {name = 'numpy-2.2.3-cp312-cp312-win_amd64.whl', upload-time = 2025-02-13T16:51:21.821880+00:00, url = 'https://files.pythonhosted.org/packages/42/6e/55580a538116d16ae7c9aa17d4edd56e83f42126cb1dfe7a684da7925d2c/numpy-2.2.3-cp312-cp312-win_amd64.whl', size = 12626357, hashes = {sha256 = '83807d445817326b4bcdaaaf8e8e9f1753da04341eceec705c001ff342002e5d'}},
  {name = 'numpy-2.2.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl', upload-time = 2025-02-13T16:50:00.079662+00:00, url = 'https://files.pythonhosted.org/packages/39/04/78d2e7402fb479d893953fb78fa7045f7deb635ec095b6b4f0260223091a/numpy-2.2.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl', size = 16116679, hashes = {sha256 = '3b787adbf04b0db1967798dba8da1af07e387908ed1553a0d6e74c084d1ceafe'}},
]

[tool.mousebender]
command = ['.', 'lock', '--platform', 'cpython3.12-windows-x64', '--platform', 'cpython3.12-manylinux2014-x64', 'cattrs', 'numpy']
run-on = 2025-03-06T12:28:57.760769

설치

다음은 잠금 파일로부터 설치할 때 거쳐야 하는 단계를 개략적으로 설명합니다(요구 사항은 규범적이지만, 전반적인 단계와 순서는 제안 사항입니다):

  1. 설치할 추가 기능과 의존성 그룹을 모으고, 마커 평가를 위해 각각 extras와 dependency_groups를 설정합니다.
    1. extras는 기본적으로 빈 집합으로 설정해야 합니다.
    2. dependency_groups는 기본적으로 default-groups로부터 만든 집합이어야 합니다.
  2. lock-version에 지정된 메타데이터 버전이 지원되는지 확인합니다. 상황에 따라 반드시 오류나 경고를 발생시켜야 합니다.
  3. requires-python이 지정되어 있으면 설치 대상 환경이 해당 요구 사항을 충족하는지 확인합니다. 충족하지 않으면 반드시 오류를 발생시켜야 합니다.
  4. environments가 지정되어 있으면 환경 마커 표현식 중 적어도 하나가 충족되는지 확인합니다. 충족되는 표현식이 없으면 반드시 오류를 발생시켜야 합니다.
  5. [[packages]]에 나열된 각 패키지에 대해 다음을 수행합니다:
    1. marker가 지정되어 있으면 충족되는지 확인합니다. 충족되지 않으면 다음 패키지로 넘어갑니다.
    2. requires-python이 지정되어 있으면 충족되는지 확인합니다. 충족되지 않으면 반드시 오류를 발생시켜야 합니다.
    3. 충돌하는 다른 패키지 인스턴스가 설치 예정으로 지정되어 있지 않은지 확인하십시오. 지정되어 있다면 모호성에 대한 오류를 반드시 발생시켜야 합니다.
    4. 패키지의 소스가 적절하게 지정되어 있는지(즉, 패키지 항목에 서로 충돌하는 소스가 없는지) 확인하십시오. 문제가 발견되면 반드시 오류를 발생시켜야 합니다.
    5. 해당 패키지를 설치할 패키지 집합에 추가하십시오.
  6. 설치할 각 패키지에 대해 다음을 수행하십시오:
    • vcs가 설정된 경우:
      1. commit-id에 지정된 커밋 ID로 저장소를 클론하십시오.
      2. subdirectory를 고려하여 패키지를 빌드하십시오.
      3. 설치하십시오.
    • 그렇지 않고 directory가 설정된 경우:
      1. subdirectory를 고려하여 패키지를 빌드하십시오.
      2. 설치하십시오.
    • 그렇지 않고 archive가 설정된 경우:
      1. 파일을 가져오십시오.
      2. 파일 크기와 해시를 검증하십시오.
      3. subdirectory를 고려하여 패키지를 빌드하십시오.
      4. 설치하십시오.
    • 그렇지 않고 wheels에 대한 항목이 있는 경우:
      1. name을 기준으로 적절한 wheel 파일을 찾으십시오. 찾지 못하면 sdist로 넘어가거나, 프로젝트의 소스가 없다는 오류를 반드시 발생시켜야 합니다.
      2. 파일을 가져오십시오:
        • path가 설정되어 있으면 이를 사용하십시오.
        • url이 설정되어 있으면 이를 사용해 보십시오. 선택적으로 도구는 선택된 wheel 파일을 다운로드하기 위해 packages.index 또는 도구별 메커니즘을 사용할 수 있습니다(도구는 사용 가능한 파일에 따라 다운로드할 wheel 파일을 바꾸려고 해서는 절대 안 됩니다. 재현성을 위해 설치할 파일은 오프라인 방식으로 결정되어야 합니다).
      3. 파일 크기와 해시를 검증하십시오.
      4. 설치하십시오.
    • 그렇지 않고 wheel 파일을 찾지 못했거나 sdist만 설정된 경우:
      1. 파일을 가져오십시오.
        • path가 설정되어 있으면 이를 사용하십시오.
        • url이 설정되어 있으면 이를 사용해 보십시오. 도구는 파일을 다운로드하기 위해 packages.index 또는 도구별 메커니즘을 사용할 수 있습니다.
      2. 파일 크기와 해시를 검증하십시오.
      3. 패키지를 빌드하십시오.
      4. 설치하십시오.

requirements.txt 파일과의 의미상 차이점

형식을 제외하면, 이 PEP에서 제안하는 잠금 파일과 requirements file로 가능한 것 사이에는 몇 가지 차이점이 있습니다.

차이점 중 일부는 보안과 관련이 있습니다. 해시를 필수로 요구하고, 파일 크기와 파일을 찾은 위치(색인과 파일 자체의 위치 모두)를 기록하면 잠금 대상이 된 파일을 감사하고 검증하는 데 도움이 됩니다. 이를 요구 사항 파일과 비교해 보십시오. 요구 사항 파일도 해시를 선택적으로 포함할 수 있지만, 이는 사용자가 직접 켜야 하는 기능이며 우회할 수도 있습니다. 파일의 업로드 시각과 파일을 찾을 수 있는 위치를 선택적으로 포함할 수 있다는 점도 다릅니다.

파일 전체에 대해 지원하는 Python 버전과 환경을 명시한다는 점도 이 PEP만의 특징입니다. 이는 요구 사항 파일이 언제 특정 플랫폼을 대상으로 하는지 알 수 없는 문제를 완화하기 위한 것입니다.

[tool] 테이블에 직접 대응하는 요소가 요구 사항 파일에는 없습니다. 요구 사항 파일도 주석은 지원하지만, TOML로 작성되어 구조를 갖춘 [tool] 테이블과 달리 본질적으로 구조화되어 있지 않습니다.

요구 사항 파일의 주석으로도 잠금 파일의 내용을 감사하고 이해하는 데 유용한 세부 정보를 기록할 수 있지만, 그러한 정보를 기록하기 위한 구조화된 지원을 제공하면 감사가 더 쉬워집니다. 패키지에 필요한 Python 버전을 미리 기록하면 이러한 감사에 도움이 될 뿐만 아니라, 설치가 실패할 경우 더 일찍 오류를 낼 수 있습니다. wheel 파일 이름을 URL이나 경로와 별도로 기록하는 것 또한 wheel 파일 목록을 더 쉽게 읽을 수 있도록 돕기 위함인데, 파일 이름에는 파일을 이해하고 감사하는 데 유용한 정보가 인코딩되어 있기 때문입니다. sdist 파일 이름을 기록하는 것도 같은 이유입니다.

이 PEP는 다용도 잠금 파일을 지원하지만, 요구 사항 파일은 단일 용도입니다.

이 PEP는 다음과 같은 이유로 요구 사항 파일을 완전히 대체하지는 않습니다.

하위 호환성

기존에 존재하는 잠금 파일 형식이 없으므로, Python 패키징 표준 측면에서 명시적인 하위 호환성 문제는 없습니다.

패키징 도구의 경우, 이 PEP를 지원할지 여부와 어떤 방식으로 지원할지(즉, 내보내기 대상으로 지원할지 아니면 잠금 파일을 기록하는 주된 방식으로 지원할지)는 각 도구가 결정할 문제입니다.

보안 영향

보안을 최우선으로 하는 자세에서 출발하는 잠금 파일 형식으로 표준화함으로써 전반적인 패키징 설치가 더 안전해지기를 기대합니다. 그러나 이 PEP가 잠재적인 보안 문제를 모두 해결하지는 않습니다.

잠재적인 문제 중 하나는 잠금 파일 변조입니다. 잠금 파일을 소스 관리 시스템에 보관하지 않고 제대로 감사하지 않으면, 악의적인 행위자가 파일을 악의적인 방식으로 변경할 수 있습니다(예: 패키지의 악성 코드 버전을 가리키도록 변경). 변조는 사용자를 대신해 설치를 수행할 클라우드 제공자 등으로 전송되는 도중에도 발생할 수 있습니다. 두 경우 모두 잠금 파일 내부의 [tool] 항목에 서명을 넣거나 잠금 파일 외부의 별도 채널을 통해 잠금 파일에 서명하면 완화할 수 있습니다.

이 PEP는 사용자가 잘못된 패키지를 설치하는 것을 막기 위한 조치를 전혀 하지 않습니다. 패키지 포함 여부를 감사하는 데 도움이 되는 세부 정보를 많이 담고 있지만, 타이포스쿼팅을 통한 이름 혼동 공격 등을 막는 메커니즘은 없습니다. 도구가 이를 돕기 위한 UX를 제공할 수도 있습니다(예: 패키지의 다운로드 수 제공).

교육 방법

사용자가 어떤 패키지의 설치를 요청할 때, 그 패키지에는 자체 의존성이 있을 수 있고, 그 의존성에도 또 의존성이 있을 수 있으며, 이런 식으로 계속 이어질 수 있다는 점을 사용자에게 알려야 합니다. 요청한 패키지를 설치하는 과정에서 무엇이 설치되는지 기록해 두지 않으면, 사용자가 모르는 사이에 패키지 버전 같은 것이 바뀔 수 있습니다. 하위 의존성이 바뀌면 사용자의 코드가 뜻하지 않게 망가질 수 있습니다. 잠금 파일은 무엇이 설치되었는지 기록해 두는 방법을 제공하여 이 문제를 해결하는 데 도움을 줍니다. 이 기록이 있으면 나중에도 정확히 똑같은 것을 설치할 수 있습니다.

설치할 대상을 기록해 두면 다른 사람과 협업할 때도 도움이 됩니다. 잠금 파일의 내용에 합의하면 모두가 같은 패키지를 설치하게 됩니다. 이렇게 하면 프로젝트 작업자 중 일부만 설치한 특정 버전에서만 사용할 수 있는 API 같은 것에 아무도 의존하지 않도록 할 수 있습니다.

잠금 파일은 누군가 몰래 끼워 넣었을 수 있는 악성 파일이 아니라 항상 같은 파일이 설치되도록 보장하여 보안에도 도움이 됩니다. 또한 의존성을 업그레이드할 때 더 신중하게 판단할 수 있으므로, 변경 사항이 악의적인 행위자가 몰래 끼워 넣은 것이 아니라 의도한 것임을 확인할 수 있습니다.

잠금 파일은 특정 환경만 지원할 수도 있습니다. 설치 대상 환경에 설치해야 하는 것은 다른 환경에 설치해야 하는 것과 다를 수 있습니다. 하지만 일부 잠금 파일은 범용을 지향하여 가능한 모든 환경에서 동작하려고 합니다(sdist와 소스 트리 설치가 성공한다는 전제하에).

잠금 파일은 단일 용도일 수도 있고 다중 용도일 수도 있습니다. 단일 용도 잠금 파일은 하나의 사용 사례를 위한 것입니다. 다중 용도 잠금 파일은 extra와 의존성 그룹에 따라 여러 사용 사례에 쓸 수 있습니다. 다중 용도 잠금 파일을 쓸 수 있는지는 사용하는 도구가 결정합니다. 잠금 파일을 다루는 모든 도구는 적어도 단일 용도 잠금 파일은 지원합니다. 두 유형의 잠금 파일 중 어느 쪽이 더 낫거나 못한 것은 아니며, 파일 하나에 얼마나 많은 내용을 기록할 수 있는지만 달라집니다.

이 PEP를 따르는 잠금 파일은 이 명세를 구현하는 모든 설치 프로그램으로 설치할 수 있습니다. 따라서 잠금 파일 사용자는 잠금 파일을 만든 사람이 사용한 잠금 도구에 얽매이지 않고 설치를 수행할 수 있습니다. 그러나 다른 잠금 도구를 사용해도 같은 결과가 나온다는 뜻은 아닙니다. 이는 무엇을 잠금할지 결정하는 데 서로 다른 알고리즘을 사용하는 것을 비롯해 여러 가지 이유 때문일 수 있습니다.

참조 구현

단일 용도 잠금 파일을 구현한 개념 증명은 https://github.com/brettcannon/mousebender/tree/pep 에서 확인할 수 있습니다. PDM 및 Poetry 같은 다른 도구들도 이 PEP의 여러 부분에 대해 의미상 비슷한 방식을 구현하고 있습니다.

기각된 아이디어

설치를 위해 의존성 그래프 기록하기

이 PEP의 이전 버전에서는 설치할 패키지 집합 대신 패키지의 의존성 그래프를 기록했습니다. 의존성 그래프를 기록하면 더 많은 정보를 얻을 수 있을 뿐 아니라, 더 많은 기능을 본래부터 지원하므로 유연성도 높아진다는 생각이었습니다(예: 마커를 명시적으로 전파하지 않고도 플랫폼별 의존성 지원).

하지만 결국 비용을 감수할 만한 가치가 없는 복잡성을 더한다고 판단했습니다(예를 들어, 이 PEP가 목표를 이루는 데 필요하지 않은 세부 사항까지 감사해야 해서 감사가 어려워졌습니다).

파일 간에 일관된 메타데이터를 요구하는 새로운 코어 메타데이터 버전 지정하기

한때는 파일마다 메타데이터가 달라서 정확한 잠금 결과를 얻으려면 패키지와 버전에 대해 릴리스된 모든 파일을 검사해야 하는 문제를 해결하기 위해, 패키지의 단일 버전에 대한 모든 wheel 파일의 메타데이터가 모두 동일하도록 요구하는 새 코어 메타데이터 버전을 도입하자는 아이디어가 제기되었습니다. 하지만 결국 이 PEP가 성능상의 이유로 사람들이 파일을 일관되게 만들거나, 색인이 wheel 파일 자체와 별도로 모든 메타데이터를 제공하도록 압력을 가할 것이므로 이는 불필요하다고 판단되었습니다. 또한 이를 강제할 쉬운 수단이 없으므로, 커뮤니티의 기대만으로도 새로운 메타데이터 버전만큼 효과를 낼 수 있을 것입니다.

설치 프로그램이 의존성 해석을 수행하도록 하기

이 PEP의 초안이 작성될 당시 Poetry가 동작하던 방식에 더 가까운 형식을 지원하기 위해, 잠금 도구가 가능한 모든 시나리오에서 설치가 이루어지는 데 필요할 수 있는 패키지와 그 버전을 사실상 모두 기록하고, 그런 다음 설치 프로그램이 무엇을 설치할지 해석하도록 하자는 제안이 있었습니다. 그러나 그렇게 하면 주어진 시나리오에서 어떤 패키지가 설치될 수 있는지 파악하는 데 훨씬 더 많은 정신적 노력이 필요해지므로 잠금 파일을 감사하기가 복잡해집니다. 또한 Poetry 개발자 중 한 명은 이 PEP의 패키지 잠금 방식에서 표현되는 마커만으로도 Poetry의 요구를 충족하기에 충분할 수 있다고 제안했습니다. 설치 프로그램이 해석을 수행하지 않도록 하면 설치 프로그램의 구현도 단순해지고, 복잡성은 잠금 도구에 집중됩니다.

최소 해시 알고리즘 지원 요구하기

파일에 대해 기준이 되는 해시 알고리즘을 요구하자는 제안이 있었습니다. 다른 어떤 Python 패키징 명세도 특정 해시 알고리즘 지원을 요구하지 않으므로 이 제안은 거부되었습니다. 또한 제안된 최소 해시 알고리즘이 언젠가는 낡거나 안전하지 않은 권장 사항이 되어 추가 갱신이 필요해질 수 있습니다. 항상 최선의 알고리즘을 사용하도록 장려하기 위해, 도구가 해당 해시 알고리즘의 보안상 영향을 고려하지 않고 단순히 기준 알고리즘을 기본값으로 쓰는 일을 막고자 기준을 제공하지 않습니다.

파일 이름 짓기

*.pylock.toml을 파일 이름으로 사용하기

파일 이름에서 pylock 상수 부분을 잠금 파일의 용도를 나타내는 식별자 뒤에 두자는 제안이 있었습니다. 잠금 파일이 순전히 용도에 따라 정렬되어 디렉터리 안에 흩어지는 대신, 디렉터리 내용을 볼 때 한데 모여 정렬되도록 이 방식을 채택하지 않기로 결정했습니다.

*.pylock을 파일 이름으로 사용하기

.toml을 파일 확장자로 사용하지 않고 대신 .pylock 자체를 확장자로 삼자는 제안이 있었습니다. 코드 편집기가 파일 확장자에 대한 특별한 지식 없이도 잠금 파일에 문법 강조를 제공하는 방법을 알 수 있도록 이 제안은 채택하지 않기로 결정되었습니다.

파일 이름 규칙을 두지 않기

잠금 파일 이름에 대한 요구 사항이나 지침을 전혀 두지 않는 방안도 검토되었지만 결국 기각되었습니다. 표준화된 이름 규칙이 있으면 사람과 코드 편집기 모두 잠금 파일을 쉽게 식별할 수 있습니다. 이는 예를 들어 도구가 사용 가능한 모든 잠금 파일을 알고자 할 때 파일을 찾는 데 도움이 됩니다.

파일 형식

TOML 대신 JSON 사용하기

기계가 작성할 수 있는 형식을 갖추는 것이 이 PEP의 목표 중 하나였으므로 JSON을 사용하자는 제안이 있었습니다. 그러나 JSON은 TOML보다 사람이 읽기 어려운 반면, 기계 작성 측면에서는 변경을 정당화할 만큼 충분히 개선되지 않는다고 판단되었습니다.

TOML 대신 YAML 사용하기

일부에서는 YAML이 TOML보다 기계 작성 가능성과 사람의 가독성 요구 사항을 더 잘 충족한다고 주장했습니다. 하지만 이는 주관적인 문제이고, pyproject.toml이 이미 Python 패키징 표준에서 사용하는 사람이 작성할 수 있는 파일로 존재했기 때문에 TOML을 계속 사용하는 것이 더 중요하다고 판단되었습니다.

기타 키

파일 전체에 대한 단일 해시 알고리즘

이 PEP의 이전 버전에서는 파일마다 알고리즘을 개수 제한 없이 지정하는 대신 파일마다 단일 해시 알고리즘을 지정하도록 제안했습니다. 단일 알고리즘을 지정하면 특정 해시 알고리즘의 사용이 의무화된 경우 파일을 감사하는 데 도움이 될 것이라는 생각이었습니다.

결국 이 아이디어에는 몇 가지 반대 의견이 있었습니다. 반대 의견은 대체로 대용량 wheel 파일(예: PyTorch)을 다시 해시하는 비용에 집중되었습니다. 또한 설치 프로그램이 동의하지 않을 수도 있는 해시 관련 결정을 설치 프로그램을 대신하여 미리 내리는 것에 대한 우려도 있었습니다. 결국 유연성을 확보하고 사람들이 적절하다고 생각하는 방식으로 잠금 파일을 감사하도록 하는 편이 더 낫다고 판단되었습니다.

잠금 파일 자체의 내용 해시하기

한때 파일의 바이트 내용을 해시하고 그 해시값을 파일 자체에 저장하는 방안이 제안되었습니다. 병합할 때마다 병합 충돌을 피하려면 해시값을 다시 계산해야 하므로, 잠금 파일의 변경 사항을 더 쉽게 병합할 수 있도록 이 방안은 제거되었습니다.

파일의 의미적 내용을 해시하는 방안도 제안되었지만, 이 역시 같은 병합 충돌 문제로 이어집니다.

어떤 내용을 해시하든, 그러한 해시가 필요하다면 두 방식 모두 해시값을 파일 외부에 저장할 수 있습니다.

잠금 파일의 생성 날짜 기록하기

잠금 파일이 얼마나 오래되었을 수 있는지 알 수 있도록, 이전 제안에서는 잠금 파일의 생성 날짜를 기록하자고 제안했습니다. 하지만 파일 내용의 해시를 저장하는 경우와 같은 병합 충돌 문제 때문에 이 아이디어는 폐기되었습니다.

검색에 사용된 패키지 색인 기록하기

잠금 파일을 생성하는 데 어떤 패키지 색인이 사용되었는지 기록하는 방안이 검토되었습니다. 그러나 결국 불필요한 기록 관리라고 판단되어 기각되었습니다.

sdist의 빌드 요구 사항 잠그기

이 PEP의 이전 버전에서는 packages.build-requires 키 아래에 sdist의 빌드 요구 사항을 잠금하려고 했습니다. 안타깝게도 이 방식이 어떻게 동작해야 하는지를 두고 적지 않은 사람들이 혼란을 겪었고, 엣지 케이스 문제도 상당히 많아서 이 PEP에서 미리 다룰 가치가 없다고 결정했습니다. 대신 향후 PEP에서 해결책을 제안할 수 있습니다.

전용 direct 키

이전 버전에는 무언가가 직접 URL 참조에서 비롯된 것으로 간주되어야 할 때 이를 플래그로 표시하기 위한 전용 packages.direct 키가 있었습니다. 하지만 직접 URL 참조가 발생할 수 있는 경우는 명시적으로 세 가지(VCS, 디렉터리, 아카이브)뿐입니다. 세 가지 경우 모두 [[packages]]에 명시되어 있으므로, 이 키를 설정하는 것은 기술적으로 불필요했습니다.

이 키가 없을 때의 유일한 단점은 이제 packages.archive에 속하게 된 휠과 sdist에 관한 것입니다. 별도의 키가 있으면(또는 packages.sdist나 packages.wheels의 일부로 지정되어 있으면), 아카이브 파일이 sdist인지 휠인지를 잠금 파일 자체에서 식별할 수 있습니다. 현재로서는 설치 프로그램이 그 세부 사항을 스스로 추론해야 합니다.

단순화

패키지 버전 기록 삭제

패키지 버전은 sdist 또는 wheel 파일을 사용할 때만 안정적으로 기록할 수 있으므로 선택 사항입니다. 또한 두 소스 모두 파일 이름에 버전을 기록하므로 기술적으로는 중복입니다.

하지만 논의 과정에서 버전 번호가 감사(auditing)에 충분히 유용하므로 여전히 별도로 명시하기로 결정되었습니다.

sdist 및/또는 wheel의 위치를 지정해야 한다는 요구 사항 제거

적어도 한 사람은 자신의 업무 환경에서 모든 sdist와 wheel의 URL이 불안정하다고 언급했습니다. 따라서 그들은 이전에 파일을 어디에서 찾았는지와 관계없이 설치 시 모든 파일을 검색해야 합니다. 파일의 URL이나 경로를 제공해야 한다는 요구 사항을 없앴다면, 잘못된 것으로 알려진 정보를 기록하는 문제를 해결하는 데 도움이 되었을 것입니다.

제공된 URL 외에 다른 방법으로도 도구가 파일을 찾을 수 있도록 허용하기로 결정하면서 URL을 선택 사항으로 만들 필요성이 줄어들었습니다.

파일 크기와 해시 요구 사항 제거

적어도 한 사람은 자신의 업무 환경에서 모든 wheel과 sdist에 내부 파일을 넣어 수정한다고 말했습니다. 이는 기록된 해시와 파일 크기가 모두 틀리게 된다는 뜻입니다. 파일 크기와 해시를 선택 사항으로 만든다면(아마도 어떤 옵트아웃 메커니즘을 통해), 그들은 이 PEP의 요구 사항을 충족하는 잠금 파일을 계속 생성할 수 있을 것입니다.

이는 보안을 지나치게 약화한다는 결론이 내려졌습니다. 또한 이는 대체 위치에서 파일을 설치하는 것을 불가능하게 합니다.

sdist 파일 이름 기록 제거

URL/경로 요구 사항, 패키지 버전, 해시를 제거하는 것과는 양립할 수 없지만, sdist 파일 이름을 기록하는 것은 기술적으로 전혀 필요하지 않습니다(현재 파일 이름 기록은 선택 사항입니다). 파일 이름은 프로젝트 이름과 버전만 인코딩하므로, (패키지 버전이 제공된 경우) 파일에 대한 새로운 정보를 전달하지 않습니다. 또한 위치가 기록되어 있다면 파일 이름과 관계없이 파일을 가져올 수 있습니다.

하지만 기록된 파일 위치를 더 이상 사용할 수 없을 때 적절한 파일을 찾는 데는 파일 이름을 기록해 두는 것이 도움이 될 수 있습니다(PEP 625 덕분에 이제 sdist 파일 이름이 표준화되었지만, 이는 2020년부터의 일이므로 이름을 추측할 수 없을 수도 있는 오래된 sdist가 많이 있습니다).

단순성을 위해 sdist 파일 이름을 필수로 하기로 결정했습니다.

packages.wheels를 테이블로 만들기

wheel 파일의 세부 정보를 파일 이름을 키로 하는 테이블로 작성하는 방식을 생각해 볼 수 있습니다. 예를 들면 다음과 같습니다.

[[packages]]
name = "attrs"
version = "23.2.0"
requires-python = ">=3.7"
index = "https://pypi.org/simple/"

[packages.wheels]
"attrs-23.2.0-py3-none-any.whl" = {upload-time = 2023-12-31T06:30:30.772444Z, url = "https://files.pythonhosted.org/packages/e0/44/827b2a91a5816512fcaf3cc4ebc465ccd5d598c45cefa6703fcf4a79018f/attrs-23.2.0-py3-none-any.whl", size = 60752, hashes = {sha256 = "99b87a485a5820b23b879f04c2305b44b951b502fd64be915879d77a7e8fc6f1"}}

[[packages]]
name = "numpy"
version = "2.0.1"
requires-python = ">=3.9"
index = "https://pypi.org/simple/"

[packages.wheels]
"numpy-2.0.1-cp312-cp312-macosx_10_9_x86_64.whl" = {upload-time = 2024-07-21T13:37:15.810939Z, url = "https://files.pythonhosted.org/packages/64/1c/401489a7e92c30db413362756c313b9353fb47565015986c55582593e2ae/numpy-2.0.1-cp312-cp312-macosx_10_9_x86_64.whl", size = 20965374, hashes = {sha256 = "6bf4e6f4a2a2e26655717a1983ef6324f2664d7011f6ef7482e8c0b3d51e82ac"}}
"numpy-2.0.1-cp312-cp312-macosx_11_0_arm64.whl" = {upload-time = 2024-07-21T13:37:36.460324Z, url = "https://files.pythonhosted.org/packages/08/61/460fb524bb2d1a8bd4bbcb33d9b0971f9837fdedcfda8478d4c8f5cfd7ee/numpy-2.0.1-cp312-cp312-macosx_11_0_arm64.whl", size = 13102536, hashes = {sha256 = "7d6fddc5fe258d3328cd8e3d7d3e02234c5d70e01ebe377a6ab92adb14039cb4"}}
"numpy-2.0.1-cp312-cp312-macosx_14_0_arm64.whl" = {upload-time = 2024-07-21T13:37:46.601144Z, url = "https://files.pythonhosted.org/packages/c2/da/3d8debb409bc97045b559f408d2b8cefa6a077a73df14dbf4d8780d976b1/numpy-2.0.1-cp312-cp312-macosx_14_0_arm64.whl", size = 5037809, hashes = {sha256 = "5daab361be6ddeb299a918a7c0864fa8618af66019138263247af405018b04e1"}}
"numpy-2.0.1-cp312-cp312-macosx_14_0_x86_64.whl" = {upload-time = 2024-07-21T13:37:58.784393Z, url = "https://files.pythonhosted.org/packages/6d/59/85160bf5f4af6264a7c5149ab07be9c8db2b0eb064794f8a7bf6d/numpy-2.0.1-cp312-cp312-macosx_14_0_x86_64.whl", size = 6631813, hashes = {sha256 = "ea2326a4dca88e4a274ba3a4405eb6c6467d3ffbd8c7d38632502eaae3820587"}}
"numpy-2.0.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl" = {upload-time = 2024-07-21T13:38:19.714559Z, url = "https://files.pythonhosted.org/packages/5e/e3/944b77e2742fece7da8dfba6f7ef7dccdd163d1a613f7027f4d5b/numpy-2.0.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", size = 13623742, hashes = {sha256 = "529af13c5f4b7a932fb0e1911d3a75da204eff023ee5e0e79c1751564221a5c8"}}
"numpy-2.0.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl" = {upload-time = 2024-07-21T13:38:48.972569Z, url = "https://files.pythonhosted.org/packages/2c/f3/61eee37decb58e7cb29940f19a1464b8608f2cab8a8616aba75fd/numpy-2.0.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", size = 19242336, hashes = {sha256 = "6790654cb13eab303d8402354fabd47472b24635700f631f041bd0b65e37298a"}}
"numpy-2.0.1-cp312-cp312-musllinux_1_1_x86_64.whl" = {upload-time = 2024-07-21T13:39:19.213811Z, url = "https://files.pythonhosted.org/packages/77/b5/c74cc436114c1de5912cdb475145245f6e645a6a1a29b5d08c774/numpy-2.0.1-cp312-cp312-musllinux_1_1_x86_64.whl", size = 19637264, hashes = {sha256 = "cbab9fc9c391700e3e1287666dfd82d8666d10e69a6c4a09ab97574c0b7ee0a7"}}
"numpy-2.0.1-cp312-cp312-musllinux_1_2_aarch64.whl" = {upload-time = 2024-07-21T13:39:41.812321Z, url = "https://files.pythonhosted.org/packages/da/89/c8856e12e0b3f6af371ccb90d604600923b08050c58f0cd26eac9/numpy-2.0.1-cp312-cp312-musllinux_1_2_aarch64.whl", size = 14108911, hashes = {sha256 = "99d0d92a5e3613c33a5f01db206a33f8fdf3d71f2912b0de1739894668b7a93b"}}
"numpy-2.0.1-cp312-cp312-win32.whl" = {upload-time = 2024-07-21T13:39:52.932102Z, url = "https://files.pythonhosted.org/packages/15/96/310c6f6d146518479b0a6ee6eb92a537954ec3b1acfa2894d1347/numpy-2.0.1-cp312-cp312-win32.whl", size = 6171379, hashes = {sha256 = "173a00b9995f73b79eb0191129f2455f1e34c203f559dd118636858cc452a1bf"}}
"numpy-2.0.1-cp312-cp312-win_amd64.whl" = {upload-time = 2024-07-21T13:40:17.532627Z, url = "https://files.pythonhosted.org/packages/b5/59/f6ad378ad85ed9c2785f271b39c3e5b6412c66e810d2c60934c9f/numpy-2.0.1-cp312-cp312-win_amd64.whl", size = 16255757, hashes = {sha256 = "bb2124fdc6e62baae159ebcfa368708867eb56806804d005860b6007388df171"}}

하지만 전반적으로 사람들은 이 방식을 이 PEP가 채택한 접근 방식보다 선호하지 않았습니다.

자기 참조

[tool] 테이블 제거

[tool] 테이블은 pyproject.toml 파일에서 매우 유용한 것으로 확인되었기 때문에 포함되었습니다. 이 PEP에 비슷한 유연성을 제공하는 것은 비슷한 이점이 나타나기를 기대하기 때문입니다.

하지만 일부 사람들은 이러한 테이블이 도구들에게 지나치게 매력적이어서, 특정 도구에 종속되어 다른 도구에서는 사용할 수 없는 파일이 만들어질 것을 우려했습니다. 이는 설치, 감사 등을 수행하려는 도구에 문제를 일으킬 수 있습니다. [tool] 테이블의 어떤 세부 정보가 중요한지 알 수 없기 때문입니다.

절충안으로, 이 PEP는 [tool]에 기록된 세부 정보는 폐기 가능해야 하며 패키지 설치에 영향을 주어서는 안 된다고 규정합니다.

파일의 요구 사항 입력 나열

현재 이 파일은 파일의 입력으로 사용된 요구 사항을 기록하지 않습니다. 이는 단순성을 위한 결정이며, 예상하지 못한 방식으로 파일을 명시적으로 제약하지 않으려는 목적도 있습니다(예를 들어 파일을 처음 만든 뒤 요구 사항이 다른 새 플랫폼에 맞게 파일을 갱신하는 경우, 요구 사항 전체를 어떻게 작성할지 정하지 않고도 갱신할 수 있어야 합니다).

하지만 원래 요구 사항을 어떤 방식으로든 기록해 두면 감사(auditing)나 파일 재생성에 도움이 될 수 있습니다. 이는 단일 문자열일 수도 있고, 파일에 여러 요구 사항을 사용했다면 문자열 배열일 수도 있습니다.

결국 도구가 잠금 파일을 만들 때 사용한 입력을 범용적인 방식으로 담아내는 것은 지나치게 복잡하다고 판단했습니다.

감사(auditing)

의존하는 패키지 기록

패키지를 설치하는 데 그 패키지에 의존하는 패키지를 기록할 필요는 없습니다. 따라서 이 정보는 [tool]을 통해 포함할 수 있으므로 이 PEP에서 제외했습니다.

하지만 어떤 패키지가 다른 패키지에 얼마나 중요한지 알면 유용할 수 있습니다. pip-tools가 이 정보를 포함하므로, 이를 포함한 선례가 있습니다. 의존하는 패키지를 기록할 때 유연한 방식을 사용할 수도 있습니다. 예를 들어 파일 안에서 같은 패키지의 다른 항목과 구별할 수 있을 만큼 자세히 기록하는 방식입니다(uv에서 착안했습니다).

하지만 결국 의존성을 기록하는 편이 더 낫다고 결정했습니다.

감사의 글

discuss.python.org 토론에 참여해 주신 모든 분께 감사드립니다. 또한 이 PEP를 공개하기 전에 초안에 대한 피드백을 주신 Randy Döring, Seth Michael Larson, Paul Moore, Ofek Lev께도 감사드립니다. 각 프로젝트를 대표하여 피드백을 주신 Poetry의 Randy Döring, uv의 Charlie Marsh, PDM의 Frost Ming께 감사드립니다.