
이 글의 목적은 다음과 같음:
- uv 에서 hatchling 이 어떻게 build backend로 동작하는지 이해하는 것.
pyproject.toml이 어떤 역할을 하는지 이해하는 것.
2025.04.06 - [utils] - [Tool] Builder System (or Packaging System)
[Tool] Builder System (or Packaging System)
빌더 시스템이란?빌더 시스템은 소스 코드들을 실행 파일 또는 라이브러리로 만들어주는 자동화된 처리 과정을 담당하는 도구를 가리킴.소스 코드를 실행 가능한 프로그램 또는 라이브러리 (or
ds31x.tistory.com
1. Python packing 구조: Frontend, Backend, 그리고 설정 파일
Modern Python packaging 구조는 세 가지 요소로 분리되어 있음:
| 역할 | 담당 |
| Build Frontend (빌드 실행) | uv |
| Build Backend (실제 빌드) | hatchling |
| 설정 파일 (둘을 연결) | pyproject.toml |
실제 흐름은 다음과 같음:
uv build실행.pyproject.toml의[build-system]섹션 확인.- 해당 섹션에 명시된 build backend(pure python 인 경우 hatchling 이 많이 사용됨)를 호출.
- 패키징 결과물 (wheel and sdist) 생성.
2. Modern Python Packaging: PEP 517 / PEP 518
2-1. 예전 방식
- 과거
setuptools+setup.py조합이 사실상 표준이었음 (아직까지도 복잡한 프로젝트에서 사용됨). pip이setup.py를 직접 실행 하는 방식임.
2025.12.21 - [Python] - 개발 디렉토리를 pip package로 설치하기 - pip install -e .
개발 디렉토리를 pip package로 설치하기 - pip install -e .
0. pyproject.toml 과 pip install -e . 사용 튜토리얼이 글은 개발 중인 Python code directory를 pip package로 install하는 방법을 정리함.pyproject.toml을 이용한 package metadata 정의 와pip install -e .를 활용한 editable insta
ds31x.tistory.com
2-2. 문제점
- 빌드 로직이 코드에 숨어 있음:
setup.py는 Python script (imperative) 임.- 빌드 시점에 무슨 일이 일어나는지 예측이 어려웠음.
- 빌드 시점에 명확히 알기 위해선
setup.py와 같은 실행형 대신에pyproject.toml이라는 선언형이 유리.
- 재현성(reproducibility)이 떨어짐:
- 동일한 source에서 동일한 결과가 나온다는 보장이 없음.
setup.py가 실행 시점의 환경 변수, OS, 설치된 패키지, 네트워크 상태 등 런타임 환경에 의존하는 실행코드 임.- 동일한 소스를 다른 머신에서 빌드하면 결과가 달라질 수 있음.
- Build dependency 관리가 어려움:
- 빌드에 필요한 패키지가 뭔지 명시적으로 선언하기가 쉽지 않음.
2-3. 해결방안: PEP517/518
이 문제들을 해결하기 위해 두 가지 PEP이 등장함:
- PEP 517: Build frontend 와 build backend를 분리 하는 규격.
- Frontend(예:
uv,pip,build)는 backend를 호출만 함. - Backend(예:
hatchling,setuptools,flit-core)는 실제 빌드를 담당.
- Frontend(예:
- PEP 518: Build dependency를
pyproject.toml에 명시적으로 선언 하는 규격.[build-system]섹션의requires필드가 이 역할을 수행.
오늘날 packaing system에서
- 실행은
uv(build freontend)가 하고,
빌드는hatchling(build backend)이 담당하는 방식이 가능해짐.
setup.py와 setuptools 조합과의 핵심적인 차이는 다음과 같음:
- (실행) 코드 기반(
setup.py) - 선언 기반(
pyproject.toml).
이 두 PEP 를 구현한 툴들을 통해 Python 의 modern packaging이 가능해짐.
그 중 대표적인 예가 Hatch 패키지임.
Hatch - Hatch
Modern, extensible Python project management
hatch.pypa.io
3. Hatch와 Hatchling
3-1. Hatch
- Python 프로젝트 관리 도구.
- Ofek Lev 가 2021 년 소개.
- Modern packaging 흐름 이후 등장한 올인원(all-in-one) 도구임.
hatch 의 역할:
- 가상환경(virtual environment) 관리.
- 빌드(build).
- 배포(publish).
- 버전 관리(versioning).
3-2. Hatchling
hatch에서 빌드 기능만 분리 한 것.- Build backend 임.
hatchling 의 특징:
pyproject.toml기반으로 동작.setup.py가 필요 없음.- wheel / sdist 생성을 담당.
- Pure Python package에 특히 적합함.
hatch 는 훌륭한 올인원 프로젝트 관리 도구 이나,
오늘날에는 uv (2024년 Astral)가 보다 많이 사용되는 추세임.
uv에서도 build backend로 hatchling을 사용함.
4. pyproject.toml 에서 Hatchling 선언
4-1. 핵심 설정
build-system.build-backend 에 "hatchling.build" 를 값으로 지정.
[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"
requires = ["hatchling"]:- 빌드할 때
hatchling패키지를 팔요하므로, - 이를 설치 하라는 뜻.
- 빌드할 때
build-backend = "hatchling.build":hatchling.build모듈을 build backend 로 사용하라는 뜻.
이같은 pyproject.toml이 있는 디렉토리에서uv build 를 실행하면, uv 는 이 설정을 읽고 다음을 수행함:
hatchling을 (uv의) 격리된 build environment에 설치.hatchling.build모듈의build_wheel()/build_sdist()함수를 호출.- 결과물(
wheel,sdist)을dist/디렉토리에 출력.
참고로 과거 setuptools 방법은 다음과 같음:
[build-system]
requires = ["setuptools>=61.0", "wheel"]
build-backend = "setuptools.build_meta"
[tool.setuptools.packages.find] 도 추가되어야 함. 다음을 참고:
[build-system]
requires = ["setuptools>=61.0"]
build-backend = "setuptools.build_meta"
[project]
name = "my-package"
version = "0.1.0"
description = "A minimal example package"
requires-python = ">=3.9"
[tool.setuptools.packages.find]
where = ["src"]
2025.12.21 - [Python] - PyPI에 wheel을 업로드하기-pip 이용
PyPI에 wheel을 업로드하기-pip 이용
2026.05 현재 최근 추세는 uv를 활용하는 것이 보다 권장된다. 0. 전체 workflow 개요PyPI에 wheel을 업로드하는 과정은 다음 단계로 구성됨.PyPI account 생성project metadata 준비wheel build 수행upload tool 준비PyPI
ds31x.tistory.com
5. Tutorial: 간단한 Pure Python Package 예제 만들기
이 글에서 사용하는 예제는 다음 조건을 가짐:
- Pure Python package.
- 외부 dependency 없음.
- 최소 구조(minimal structure).
5-1. 개발 디렉토리 구조
my_project/
├ pyproject.toml
└ src/
└ my_package/
└ __init__.py
pip등으로 설치 후import my_package를 통해 모듈 사용 가능.
5-2. pyproject.toml
[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"
[project]
name = "my-package"
version = "0.1.0"
description = "A minimal example package"
requires-python = ">=3.9"
[tool.hatch.build.targets.wheel]
packages = ["src/my_package"]
[build-system]: 앞서 설명한 build backend 선언.[project]: 패키지 메타데이터.name,version은 필수 필드임.
[tool.hatch.build.targets.wheel]: wheel 빌드 시 **패키지 위치를 지정**.srclayout을 사용하는 경우, hatchling에게 패키지가src/아래에 있다는 것을 알려줘야 함.- 이 설정이 없으면 hatchling은 프로젝트 이름(
my-package→my_package)을 기준으로 자동 탐지를 시도함. - 자동 탐지가 실패하면
ValueError: Unable to determine which files to ship inside the wheel에러가 발생함. - 명시적으로 선언하는 것이 안전함.
5-3. src/my_package/__init__.py
"""Minimal example package."""
def hello():
return "Hello from my_package!"
__init__.py는 해당 디렉토리를 Python package로 인식 시키는 파일임.__init__.py에 정의된 함수나 변수는 패키지를 import할 때 바로 접근 가능 함:from my_package import hello # __init__.py 의 hello() 를 직접 import- 이 예제에서는 빌드 흐름 확인이 목적이므로, 단순한
hello()함수 하나만 구현함.
5-4. 빌드 실행
pyproject.toml 이 위치한 프로젝트 루트 디렉토리 에서 실행해야 함:
cd my_project/ # pyproject.toml 이 있는 디렉토리로 이동
uv build
uv는 현재 디렉토리의pyproject.toml을 찾아 읽음.pyproject.toml이 없으면 빌드가 실패함.- 명시적으로 경로를 지정하고 싶은 경우
uv build --directory path/to/my_project를 사용할 수 있음.
실행 결과, dist/ 디렉토리에 다음 두 파일이 생성됨:
dist/
├── my_package-0.1.0-py3-none-any.whl
└── my_package-0.1.0.tar.gz
참고: uv build 가 내부적으로 수행하는 일 (detail)
pyproject.toml의[build-system]섹션을 읽음.requires에 명시된hatchling을 격리된 임시 build environment 에 설치.- 프로젝트의 가상환경과는 별개임.
- 빌드가 끝나면 이 임시 환경은 사라짐.
build-backend에 지정된hatchling.build모듈의 빌드 함수를 호출:build_wheel()→.whl생성.build_sdist()→.tar.gz생성.
- 결과물을
dist/디렉토리에 출력.
uv build는 기본적으로
wheel과 sdist를 둘 다 생성함.
wheel만 필요한 경우uv build --wheel,
sdist만 필요한 경우uv build --sdist
를 사용하면 됨.
중요: PEP517을 따르는 build frontends
uv build나 python -m build, hatch build 모두 PEP517을 따르므로,
이들 모두 pyproject.toml의 [build-system]을 읽고 hatchling.build의 build_wheel() / build_sdist()를 호출함.
즉, 결과물은 동일.
5-5. 빌드 결과물: wheel과 sdist
빌드를 하면 두 가지 결과물이 생성됨:
| 결과물 | 설명 |
wheel (.whl) |
바로 설치 가능한 배포 파일(pre-built distribution). |
sdist (.tar.gz) |
Source 코드 압축본(source distribution). |
wheel은 설치용,
sdist는 재빌드용 .
wheel: 설치 시 빌드 과정 없이 바로 unpack하여 사용. 빠름.sdist: 설치 시 빌드 과정이 필요함. 빌드 환경이 갖춰져야 함.
참고: Wheel 이름 규칙과 ABI
Wheel 파일명 예시
my_package-0.1.0-py3-none-any.whl
파일명의 각 부분을 분리하면 다음과 같음:
| 부분 | 값 | 의미 |
| Package name | my_package |
패키지 이름 |
| Version | 0.1.0 |
버전 |
| Python tag | py3 |
Python 3 |
| ABI tag | none |
ABI 없음 (pure Python) |
| Platform tag | any |
플랫폼 독립(OS 무관) |
참고: ABI (Application Binary Interface)란?
ABI는 binary compatibility(이진 호환성) 규칙 임.
https://dsaint31.tistory.com/503#Application%20Binary%20Interface%20(ABI)-1-3
[Programming] Application Programming Interface (API) and Application Binary Interface (ABI)
API란 (ABI는 아래에 있음)Application Programming Interface (API)는 서로 다른 S/W Application이 source code 수준 에서 통신할 수 있도록 하는 Protocol 및 definition으로 구성된 Interface임.즉, API는 여러 S/W Application들
dsaint31.tistory.com
C extension을 포함 하는 패키지는 특정 Python 버전의 binary interface에 의존하게 됨.
이 경우 ABI tag가 붙음:
cp312: CPython 3.12 전용 으로 빌드된 wheel.abi3: Stable ABI 를 사용하는 wheel.
Stable ABI 한 줄 정리:
특정 Python minor version에 묶이지 않도록,
제한된 C API(Limited API)만 사용하여
여러 Python 3 버전에서 동작 가능하게 하는 ABI.
Pure Python package의 경우 C extension이 없으므로, ABI tag는 none 이 됨.
이 예제에선 py3-none-any 이 만들어짐.
참고: C extension
Python은 인터프리터 언어라서 실행속도 측면에서 불리한 부분이 있음.
이를 해결하기 위해 실행 속도와 같은 성능이 중요한 코드를
C로 작성하고 Python에서 호출할 수 있게 만든 것이 C extension 임.
컴파일된 공유라이브러리(.so, .dll, .pyd등)을 포함한다는 점에서
binding과의 유사한데, 차이점은 다음과 같음:
- binding은 기존의 C, C++로 작성된 라이브러리를 Python에서 사용가능하게 해줌.
- extension은 Python을 위해 새로 C, C++로 새로 작성된 경우임.
2024.06.06 - [Python] - [Programming] Binding: Name Binding and Language Binding
[Programming] Binding: Name Binding and Language Binding
Programming에서의 Binding(binding)이란?binding은 프로그램이 실행되는 동안 특정 identifier (or name)에어떤 속성이나 객체 등의 실제 대상을 binding(연결)하는 것을 의미함.Binding의 기본 개념Name or Identifier Us
ds31x.tistory.com
5-6. CLI 명령어(Script) 추가하기
이 절은 build system의 핵심은 아니지만,
wheel 설치 시 일어나는 일을 이해하는 데 도움이 되는 내용이므로
간단히 다룸.
5-6-1. CLI용 함수 준비
앞서 만든 hello() 는 문자열을 return 만 하고 출력하지 않으므로,
CLI entry point로 쓰려면 별도의 함수를 만드는 것이 좋음:
# src/my_package/__init__.py
"""Minimal example package."""
def hello():
return "Hello from my_package!"
def main():
"""CLI entry point."""
print(hello())
5-6-2. pyproject.toml 에 [project.scripts] 추가
다음과 같은 내용을 추가:
[project.scripts]
my-cli = "my_package:main"
| 부분 | 의미 |
my-cli |
터미널에서 입력할 CLI command 이름 |
my_package |
import할 패키지 |
: |
패키지와 함수를 구분하는 구분자 |
main |
호출할 함수 |
즉, my-cli 를 실행하면 내부적으로 my_package.main() 이 호출됨.
5-6-3. 빌드 > 설치 > 실행
- 빌드만 해서는 CLI command가 생기지 않음.
- 반드시 설치 해야 함:
# 1. 빌드
cd my_project/
uv build
# 2. 빌드된 wheel 설치
uv pip install dist/my_package-0.1.0-py3-none-any.whl
# 3. CLI command 실행
my-cli
# 출력: Hello from my_package!
5-6-4. 설치 시 내부적으로 일어나는 일
uv pip install이 wheel (.whl) 파일을 열음.- wheel은 사실 zip 파일 임.
- 확장자만
.whl인 것.
- wheel 안의
entry_points.txt를 읽음:
6. 실행 흐름 정리
다음의 동작 순서를 기억할 것.
uv build
↓
pyproject.toml 의 [build-system] 읽음
↓
hatchling 을 build environment에 설치
↓
hatchling.build 의 build_wheel() / build_sdist() 호출
↓
dist/ 에 wheel, sdist 생성
uv: Build Frontend (실행 담당).hatchling: Build Backend (빌드 담당).pyproject.toml: 둘을 연결하는 선언적 설정 파일 .
요약
Python packaging의 기본 구조는 다음과 같음:
- 과거:
setup.py기반 : 코드 기반(imperative). - 현재:
pyproject.toml기반 : 선언 기반(declarative) .
이 변화의 근거는 다음과 같음:
- PEP 517: Frontend/Backend 분리.
- PEP 518: Build dependency 선언.
같이 보면 좋은 자료들
2025.04.06 - [utils] - [Tool] Builder System (or Packaging System)
[Tool] Builder System (or Packaging System)
빌더 시스템이란?빌더 시스템은 소스 코드들을 실행 파일 또는 라이브러리로 만들어주는 자동화된 처리 과정을 담당하는 도구를 가리킴.소스 코드를 실행 가능한 프로그램 또는 라이브러리 (or
ds31x.tistory.com
'Python' 카테고리의 다른 글
| pip install 옵션 정리 (0) | 2026.05.03 |
|---|---|
| [VSCode] # %% : Code Cell Marker (1) | 2026.04.19 |
| Namespace, Scope, Frame, and Context (0) | 2026.04.12 |
| pytest - tutorial (0) | 2026.04.01 |
| Windows Python install manager 설치하기 (0) | 2026.03.17 |