PEP 662 – 가상 휠을 통한 편집 가능 설치
- Author:
- Bernát Gábor <gaborjbernat at gmail.com>
- Sponsor:
- Brett Cannon <brett at python.org>
- Discussions-To:
- Discourse thread
- Status:
- Rejected
- Type:
- Standards Track
- Topic:
- Packaging
- Created:
- 28-May-2021
- Post-History:
- Resolution:
- Discourse thread
번역·라이선스 안내
이 비공식 한국어 번역은 원문 Copyright 절의 Public Domain or CC0-1.0, whichever is more permissive 조건에 따라 제공합니다. 원저자와 공식 원문은 그대로 표시합니다. 수정되지 않은 기준 원문 · 공식 최신판
초록
이 문서는 가상 휠을 도입하여 프로젝트를 편집 가능 모드로 설치할 수 있도록, PEP 517에서 소개된 빌드 백엔드와 프런트엔드 간 통신의 확장을 설명합니다.
동기
개발 중에 많은 Python 사용자는 추가 설치 단계 없이 이후 인터프리터 호출에서 기반 소스 코드와 리소스의 변경 사항이 자동으로 반영되도록 라이브러리를 설치하는 것을 선호합니다. 이 모드는 일반적으로 “개발 모드” 또는 “편집 가능 설치”라고 합니다. 현재는 실제로 관찰된 동작의 복잡성 때문에 PEP 517에서 명시적으로 제외되었으므로, 이를 수행할 표준화된 방법이 없습니다.
현재 사용자는 이 동작을 얻기 위해 다음 중 하나를 수행합니다.
- Python 코드만 대상으로 하는 경우 관련 소스 디렉터리를
sys.path에 추가합니다(명령줄 인터페이스에서PYTHONPATH환경 변수를 통해 구성할 수 있습니다). 이 경우 사용자가 프로젝트 의존성을 직접 설치해야 하며, 진입점이나 프로젝트 메타데이터는 생성되지 않는다는 점에 유의하십시오. - setuptools_는 setup.py develop 메커니즘을 제공합니다. 이 메커니즘은 인터프리터 시작 시 프로젝트 루트를
sys.path에 주입하는pth파일을 설치하고, 프로젝트 메타데이터를 생성하며, 프로젝트 의존성도 설치합니다. pip_는 pip install -e 명령줄 인터페이스를 통해 이 메커니즘을 호출할 수 있도록 제공합니다. - flit_는 프로젝트 파일을 인터프리터의
purelib폴더에 심볼릭 링크하고, 프로젝트 메타데이터를 생성하며, 의존성도 설치하는 flit install –symlink 명령을 제공합니다. 또한 이 방법을 사용하면 리소스 파일도 지원할 수 있다는 점에 유의하십시오.
이러한 예에서 보듯이 편집 가능 설치는 여러 방식으로 달성할 수 있으며, 현재 이를 수행하는 표준 방법은 없습니다. 게다가 편집 가능 설치를 달성하고 그것이 무엇인지 정의하는 책임이 누구에게 있는지도 명확하지 않습니다.
- 빌드 백엔드가 이를 정의하고 구현하도록 허용하는 것,
- 빌드 프론트엔드가 이를 정의하고 구현하도록 허용하는 것,
- 가능한 선택지 중 하나의 방법을 명시적으로 정의하고 표준화하는 것.
이 PEP의 작성자는 여기에는 모든 경우에 맞는 단일 해결책이 없다고 생각하며, 편집 가능 효과를 달성하는 각 방법에는 장단점이 있습니다. 따라서 이 PEP는 커뮤니티가 단일 해결책에 합의할 가능성이 낮으므로 세 번째 선택지를 거부합니다. 또한 이 책임을 프런트엔드와 빌드 백엔드 중 어느 쪽이 맡아야 하는지에 대한 의문이 남아 있습니다. PEP 660은 빌드 백엔드가 이를 맡도록 제안하는 반면, 현재 PEP는 주로 프런트엔드가 맡도록 제안하지만 백엔드가 원할 경우 제어권을 가질 수 있도록 허용합니다.
근거
PEP 517은 “Editable installs”를 미루었는데, 이를 포함하면 채택이 더 지연되었을 것이며 편집 가능 설치를 어떻게 구현해야 하는지에 대한 합의도 없었기 때문입니다. setuptools 및 pip 프로젝트의 인기로 인해 기존 방식이 유지되었으며, 백엔드는 사용자가 pip install -e를 통해 실행할 수 있는 setup.py develop 구현을 제공하여 편집 가능 모드를 달성할 수 있었습니다. 빌드 백엔드와 프론트엔드 간의 편집 가능 인터페이스를 정의하면 setup.py 파일과 현재의 통신 방식을 제거할 수 있습니다.
용어 및 목표
이 PEP는 프론트엔드와 백엔드의 역할을 명확히 구분하고, 각 역할의 개발자가 사용자에게 유용한 기능을 최대한 제공할 수 있도록 하는 것을 목표로 합니다. 이 제안에서 백엔드의 역할은 프로젝트를 편집 가능 설치를 위해 준비한 다음, 프론트엔드가 편집 가능 설치를 구현하고 적용할 수 있도록 충분한 정보를 제공하는 것입니다.
백엔드가 프런트엔드에 제공하는 정보는 PEP 427에 명시된 기존 사양을 따르는 휠입니다. 아카이브 자체에 대한 휠 메타데이터({distribution}-{version}.dist-info/WHEEL)에는 Editable키와 true값을 포함해야 합니다.
그러나 휠 내에 프로젝트 파일을 제공하는 대신, 프론트엔드에 노출할 파일을 정의하는 editable.json 파일을 휠의 루트 수준에 제공해야 합니다. 이 파일의 내용은 스킴 매핑 내에서 절대 소스 트리 경로를 인터프리터의 상대 대상 목적지 경로에 대응시키는 매핑으로 구성됩니다.
이전 두 문단을 충족하는 휠은 가상 휠입니다. 프런트엔드의 역할은 가상 휠을 가져와 프로젝트를 편집 가능 모드로 설치하는 것입니다. 이를 수행하는 방식은 전적으로 프런트엔드에 달려 있으며 구현 세부 사항으로 간주합니다.
편집 가능 설치 모드는 설치 중인 프로젝트의 소스 코드가 로컬 디렉터리에서 사용 가능함을 의미합니다. 프로젝트가 편집 가능 모드로 설치되면 로컬 소스 트리의 프로젝트 코드에 대한 일부 변경 사항은 새로운 설치 단계 없이 적용됩니다. 최소한 설치 시점에 존재했던 비생성 파일의 텍스트 변경 사항은 이후 패키지를 임포트할 때 반영되어야 합니다.
엔트리 포인트를 추가하거나 수정하는 경우 또는 새로운 의존성을 추가하는 경우와 같은 일부 변경 사항은 적용되려면 새로운 설치 단계가 필요합니다. 이러한 변경 사항은 일반적으로 pyproject.toml과 같은 빌드 백엔드 구성 파일에서 이루어집니다. 이러한 요구 사항은 이러한 수정 사항이 재설치 후에만 적용될 것이라는 일반적인 사용자의 기대와 일치합니다.
사용자는 편집 가능 설치가 표준 설치와 동일하게 동작하기를 기대하지만, 이것이 항상 가능하지는 않으며 다른 사용자의 기대와 충돌할 수도 있습니다. 프런트엔드가 편집 가능 모드를 구현하는 방식에 따라 일부 차이가 나타날 수 있으며, 예를 들어 소스 트리나 인터프리터의 설치 경로에 일반적인 설치와 비교하여 추가 파일이 존재할 수 있습니다.
프런트엔드는 편집 가능 설치와 표준 설치의 동작 차이를 최소화하도록 노력하고 알려진 차이점을 문서화해야 합니다.
참고로, 편집 불가능 설치는 다음과 같이 작동합니다.
- 개발자는 프로젝트 개발을 진행하기 위해 여기서 프런트엔드라고 부를 도구(예: pip)를 사용합니다. 사용자가 프로젝트의 패키지 빌드 및 설치를 트리거하려는 경우, 사용자는 프런트엔드와 통신합니다.
- 프런트엔드는 빌드 프런트엔드를 사용하여 휠 빌드를 트리거합니다(예: build). 빌드 프런트엔드는 PEP 517을 사용하여 빌드 백엔드(예: setuptools)와 통신하며, 빌드 백엔드는 PEP 518 환경에 설치되어 있습니다. 호출되면 백엔드는 휠을 반환합니다.
- 프런트엔드는 휠을 가져와 설치 관리자(예: installer)에게 전달하여 대상 Python 인터프리터에 휠을 설치합니다.
메커니즘
이 PEP는 PEP 517 백엔드 인터페이스에 두 개의 선택적 훅을 추가합니다. 훅 중 하나는 편집 가능 설치의 빌드 의존성을 지정하는 데 사용됩니다. 다른 훅은 프런트엔드가 편집 가능 설치를 만드는 데 필요한 정보를 빌드 프런트엔드를 통해 반환합니다.
get_requires_for_build_editable
def get_requires_for_build_editable(config_settings=None):
...
이 훅은 pyproject.toml 파일에 지정된 의존성 사양에 더하여 PEP 508 의존성 사양을 포함하는 문자열의 추가 시퀀스를 반드시 반환해야 합니다. 프런트엔드는 build_editable 훅이 호출되는 빌드 환경에서 이러한 의존성을 사용할 수 있도록 해야 합니다.
정의되지 않은 경우 기본 구현은 []을 반환하는 것과 같습니다.
prepare_metadata_for_build_editable
def prepare_metadata_for_build_editable(metadata_directory, config_settings=None):
...
이 훅은 지정된 metadata_directory 안에 휠 메타데이터를 포함하는 .dist-info 디렉터리를 생성해야 합니다(즉, {metadata_directory}/{package}-{version}.dist-info/와 같은 디렉터리를 생성합니다). 이 디렉터리는 휠 사양에 정의된 유효한 .dist-info 디렉터리여야 하지만, RECORD 또는 서명을 포함할 필요는 없습니다. 훅은 이 디렉터리 안에 다른 파일도 생성할 수 있으며, 빌드 프런트엔드는 이러한 파일을 보존하되 그 외에는 무시해야 합니다. 이는 메타데이터가 빌드 시점의 결정에 의존하는 경우 빌드 백엔드가 실제 휠 빌드 단계에서 재사용할 수 있도록 이러한 결정을 편리한 형식으로 기록해야 할 수 있기 때문입니다.
이 훅은 생성한 .dist-info 디렉터리의 전체 경로가 아닌 기본 이름을 유니코드 문자열로 반환해야 합니다.
빌드 프런트엔드에 이 정보가 필요하지만 해당 메서드가 정의되어 있지 않다면, build_editable을 호출하고 결과 메타데이터를 직접 확인해야 합니다.
build_editable
def build_editable(self, wheel_directory, config_settings=None,
metadata_directory=None):
...
지정된 wheel_directory에 .whl 파일을 빌드하여 배치해야 합니다. 생성한 .whl 파일의 기본 이름(전체 경로가 아님)을 유니코드 문자열로 반환해야 합니다. 휠 파일은 용어 정의 섹션에 정의된 가상 휠 유형이어야 합니다.
빌드 프런트엔드가 이전에 prepare_metadata_for_build_editable을 호출했고, 이 호출로 생성되는 휠의 메타데이터가 이전 호출과 일치해야 한다면, 생성된 .dist-info 디렉터리의 경로를 metadata_directory인자로 제공해야 합니다. 이 인자가 제공되면 build_editable은 동일한 메타데이터를 포함하는 휠을 반드시 생성해야 합니다. 빌드 프런트엔드가 전달하는 디렉터리는 prepare_metadata_for_build_editable이 생성한 디렉터리와 완전히 동일해야 하며, 해당 메서드가 생성한 인식되지 않은 파일도 포함해야 합니다.
prepare_metadata_for_build_editable 훅을 제공하지 않는 백엔드는 build_editable의 metadata_directory 매개변수를 조용히 무시하거나, 해당 매개변수가 None이 아닌 값으로 설정되었을 때 예외를 발생시킬 수 있습니다.
소스 디렉터리는 읽기 전용일 수 있으며, 이러한 경우 백엔드는 프런트엔드가 사용자에게 표시할 수 있는 오류를 발생시킬 수 있습니다. 백엔드는 캐시 위치나 임시 디렉터리에 중간 산출물을 저장할 수 있습니다. 캐시의 존재 여부가 빌드의 최종 결과에 실질적인 차이를 만들어서는 안 됩니다.
editable.json의 내용은 다음 JSON 스키마에 대한 검사를 반드시 통과해야 합니다.
{
"$schema": "http://json-schema.org/draft-07/schema",
"$id": "http://pypa.io/editables.json",
"type": "object",
"title": "Virtual wheel editable schema.",
"required": ["version", "scheme"],
"properties": {
"version": {
"$id": "#/properties/version",
"type": "integer",
"minimum": 1,
"maximum": 1,
"title": "The version of the schema."
},
"scheme": {
"$id": "#/properties/scheme",
"type": "object",
"title": "Files to expose.",
"required": ["purelib", "platlib", "data", "headers", "scripts"],
"properties": {
"purelib": { "$ref": "#/$defs/mapping" },
"platlib": { "$ref": "#/$defs/mapping" },
"data": { "$ref": "#/$defs/mapping" },
"headers": { "$ref": "#/$defs/mapping" },
"scripts": { "$ref": "#/$defs/mapping" }
},
"additionalProperties": true
}
},
"additionalProperties": true,
"$defs": {
"mapping": {
"type": "object",
"description": "A mapping of source to target paths. The source is absolute path, the destination is relative path.",
"additionalProperties": true
}
}
}
예:
{
"version": 1,
"scheme": {
"purelib": {"/src/tree/a.py": "tree/a.py"},
"platlib": {},
"data": {"/src/tree/py.typed": "tree/py.typed"},
"headers": {},
"scripts": {}
}
}
스킴 경로는 프로젝트 소스의 절대 경로를 대상 디렉터리의 상대 경로에 매핑합니다. 이 매핑을 사용하면 백엔드가 프로젝트 소스 디렉터리의 프로젝트 레이아웃을 인터프리터가 보게 될 레이아웃으로 변경할 수 있습니다.
예를 들어 백엔드가 "purelib": {"/me/project/src": ""}를 반환하면, 이는 /me/project/src 내의 모든 파일과 모듈을 대상 인터프리터의 purelib 경로 루트에 노출한다는 의미입니다.
빌드 프런트엔드 요구 사항
빌드 프런트엔드는 빌드 백엔드가 가상 휠을 생성할 수 있도록 환경을 설정할 책임이 있습니다. 빌드 휠 훅에 대한 PEP 517의 모든 권고 사항은 여기에도 적용됩니다.
프런트엔드 요구 사항
프런트엔드는 PEP 427에 정의된 대로 가상 휠을 정확히 설치해야 합니다. 또한 editable.json에 정의된 파일도 설치할 책임이 있습니다. 설치 방식은 프런트엔드에 맡기며, 프런트엔드는 선택한 방법과 해당 방법의 제한 사항을 사용자에게 정확히 알리는 것이 권장됩니다.
프런트엔드는 PEP 610을 준수하여 설치된 배포 패키지의 .dist-info 디렉터리에 direct_url.json 파일을 생성해야 합니다. url 값은 프로젝트 디렉터리(즉, pyproject.toml을 포함하는 디렉터리)를 가리키는 file:// URL이어야 하며, dir_info 값은 {'editable': true}이어야 합니다.
프런트엔드는 편집 가능 모드로 설치할 때 prepare_metadata_for_build_editable 훅을 활용할 수 있습니다.
프런트엔드가 빌드 백엔드에서 제공한 정보로 편집 가능 설치를 수행할 수 없다고 판단하면, 실패하고 그 이유를 사용자에게 명확히 설명하는 오류를 발생시켜야 합니다.
프런트엔드는 하나 이상의 편집 가능 설치 메커니즘을 구현할 수 있으며, 사용 사례에 가장 적합한 메커니즘을 사용자가 선택하도록 할 수 있습니다. 예를 들어 pip는 편집 가능 모드 플래그를 추가하고, 사용자가 pth 파일과 심볼릭 링크 중에서 선택하도록 할 수 있습니다( pip install -e . --editable-mode=pth vs pip install -e . --editable-mode=symlink).
편집 가능 구현 예시
이 PEP가 어떻게 사용될 수 있는지 보여 주기 위해 이제 몇 가지 사례 연구를 제시합니다. 제시된 해결책은 순전히 예시를 위한 것이며 프런트엔드/백엔드에 대한 규범이 아니라는 점에 유의하십시오.
소스 트리를 있는 그대로 인터프리터에 추가하기
이는 가장 간단한 구현 중 하나로, 소스 트리를 있는 그대로 인터프리터의 scheme 경로에 추가하며, 가상 휠 내의 editable.json은 다음과 같이 보일 수 있습니다.
{
{"version": 1, "scheme": {"purelib": {"<project dir>": "<project dir>"}}}
}
그러면 프런트엔드는 다음 중 하나를 수행할 수 있습니다:
- 시작할 때 대상 인터프리터의
sys.path에 소스 디렉터리를 추가합니다. 이는 대상 인터프리터의purelib폴더에pth파일을 생성하여 수행합니다. setuptools는 현재 이 작업을 수행하며, pip install -e도 동일하게 변환합니다. 이 해결책은 빠르고 플랫폼 간 호환성이 있습니다. 그러나 이렇게 하면 전체 소스 트리가 시스템에 추가되어, 표준 설치에서는 사용할 수 없을 모듈이 노출될 가능성이 있습니다. - 폴더 또는 그 안의 개별 파일에 심볼릭 링크를 생성하기 이 방법은 flit이 flit install –symlink 를 통해 수행하는 방식입니다. 이 해결책을 사용하려면 현재 플랫폼이 심볼릭 링크를 지원해야 합니다. 그래도 개별 파일에 심볼릭 링크를 생성할 수 있으므로, 소스 트리에서 제외해야 하는 파일이 포함되는 문제를 해결할 수 있습니다.
사용자 지정 임포터 사용하기
빌드 백엔드와 대상 인터프리터 간의 더욱 견고하고 동적인 협업을 위해, 사용자 지정 임포터의 등록을 허용하는 임포트 시스템을 활용할 수 있습니다. 자세한 내용은 PEP 302를 참조하고, 그 예로 editables를 확인하십시오. 백엔드는 편집 가능 빌드 중에 새 임포터를 생성하거나 이를 추가 종속성으로 설치한 다음, pth 파일을 추가하여 인터프리터 시작 시 등록할 수 있습니다.
{
"version": 1,
"scheme": {
"purelib": {
"<project dir>/.editable/_register_importer.pth": "<project dir>/_register_importer.pth".
"<project dir>/.editable/_editable_importer.py": "<project dir>/_editable_importer.py"
}
}
}
}
여기서 백엔드는 새 모듈을 가져올 때마다 호출되는 훅을 등록하여 동적이고 필요에 따른 기능을 제공합니다. 이것이 유용할 수 있는 잠재적인 사용 사례는 다음과 같습니다:
- 소스 폴더를 노출하되 모듈 제외를 준수합니다. 백엔드는 소스 파일 로더가 소스 디렉터리에서 파일을 발견하도록 허용하기 전에 제외 테이블을 참조하여 허용 여부를 결정하는 임포트 훅을 생성할 수 있습니다.
- 어떤 프로젝트에
A.py와B.py라는 두 모듈이 있다고 합시다. 이 모듈은 소스 디렉터리에 있는 서로 별개의 두 파일이지만, 휠을 빌드하는 동안 하나의 대형 파일인project.py로 병합됩니다. 이 경우 이 PEP를 사용하면 백엔드가 가져오기 시점에 소스 파일을 읽고 메모리에서 병합한 다음 모듈로 실체화하는 임포트 훅을 생성할 수 있습니다. - 오래된 C 확장을 자동으로 업데이트합니다. 백엔드는 C 확장 소스 파일의 최종 수정 타임스탬프를 확인하는 임포트 훅을 생성할 수 있습니다. 해당 타임스탬프가 현재 C 확장 바이너리보다 최신이면, 가져오기 전에 컴파일러를 호출하여 업데이트를 실행합니다.
거부된 아이디어
이 PEP는 PEP 660과 경쟁하며 해당 제안을 거부합니다. 편집 가능 설치를 구현하는 메커니즘은 빌드 백엔드가 아니라 프런트엔드에 있어야 한다고 생각하기 때문입니다. 또한 이 접근 방식은 생태계가 편집 가능 설치 효과를 달성하기 위한 대체 수단(예: 해당 PEP에서 설명한 백엔드의 느슨한 휠 모드를 단순히 암시하는 대신 sys.path에 경로를 삽입하거나 심볼릭 링크를 사용하는 방법)을 사용할 수 있도록 합니다.
특히 PEP 660은 심볼릭 링크 지원을 추가하여 휠 파일 표준을 확장하지 않고는 심볼릭 링크를 사용해 코드와 데이터 파일을 노출하는 것을 허용하지 않습니다. 휠 형식을 확장하여 휠 자체 내의 파일이 아니라 로컬 디스크에서만 사용할 수 있는 파일을 참조하는 심볼릭 링크를 지원하는 방법은 명확하지 않습니다. 백엔드 자체 또는 백엔드가 생성한 코드가 이러한 심볼릭 링크를 생성해서는 안 된다는 점(예: 인터프리터 시작 시점)을 유의해야 합니다. 그렇게 하면 제거해야 할 파일을 프런트엔드가 기록해 두는 작업과 충돌하기 때문입니다.
마지막으로, PEP 660은 purelib 및 platlib 파일만 지원합니다. 휠 형식에서 지원하는 다른 정보 유형인 include, data 및 scripts는 의도적으로 지원하지 않습니다. 이 경로를 사용하면 프런트엔드는 심볼릭 링크 메커니즘을 통해 가능한 범위에서 이러한 파일 유형을 지원할 수 있습니다(이 기능은 어디에서나 사용할 수 있는 것은 아니며 - Windows에서는 활성화해야 합니다). 이러한 파일 유형을 전혀 지원할 가능성을 배제하기보다는 가능한 범위에서 지원을 추가하는 것이 유익하다고 생각합니다.
참고 자료
Copyright
This document is placed in the public domain or under the CC0-1.0-Universal license, whichever is more permissive.