프로젝트와 릴리즈

프로젝트(Project)

  • 패키지 이름 단위의 논리적 묶음
  • 공식 정의 : 하나의 Python 패키지 이름(name)에 해당하는 전체 집합
  • 쉽게 말해, PyPI에서 보이는 “패키지” 자체 및 그와 관련된 정보를 프로젝트라고
1
2
3
4
# 예시
requests
django
numpy

릴리즈(Release)

  • 특정 프로젝트의 배포들 중 특정 버전을 가리킨다.
1
2
3
# 예시
requests 2.31.0
requests 2.30.0

릴리즈 파일(Release file)

  • PyPI 서버에 실제로 업로드되거나 캐시되는 파일 그 자체
  • 하나의 릴리즈(version)에 속한 배포 파일(distribution artifact)
릴리즈 파일 종류 예시
wheel requests-2.31.0-py3-none-any.whl
sdist requests-2.31.0.tar.gz

프로젝트, 릴리즈, 릴리즈 파일 관계 정리

  • 관계
1
2
3
4
프로젝트 (project)
 └── 릴리즈 (release / version)
      ├── 릴리즈 파일 (wheel)
      └── 릴리즈 파일 (sdist)
  • 예시
1
2
3
4
project: requests
 └── release: 2.31.0
      ├── requests-2.31.0-py3-none-any.whl
      └── requests-2.31.0.tar.gz

패키지 만들기

패키지

  • Python 코드들을 하나의 이름으로 묶은 것
  • 그러면서 import 가능하고 배포 가능한 단위
  1. 하나 이상의 Python 모듈(.py 파일)을 포함할 것
  2. __init__.py 파일을 가지는 것이 권장됨
  3. 고유한 import 이름을 가질 것
  4. 빌드시 wheel / sdist 로 묶일 수 있을 것
1
2
3
foo.py            → 모듈
foo/              → 패키지
foo/__init__.py   → 패키지 성립 조건

패키지를 만들 때의 핵심

  • 디렉터리 구조는 src/ 레이아웃을 따른다.
  • 표준은 PEP517 / PEP518 / PEP621 을 따른다.
  • 패키지를 빌드할 때는 python -m build 명령어를 사용한다.
PEP 의미
PEP 517 빌드 시스템 표준
PEP 518 pyproject.toml 사용
PEP 621 프로젝트 메타데이터 표준

패키지 디렉터리 구성

1
2
3
4
5
6
7
8
9
my-package/
├── pyproject.toml          # ← 필수 (패키지 정의)
├── README.md               # ← 권장
├── src/
│   └── my_package/         # ← 실제 패키지 (import 이름)
│       ├── __init__.py     # ← 필수
│       ├── {core}.py       # ← 코드
│       └── {utils}.py
└── tests/                  # ← 선택
  • my-package : 프로젝트 이름 (배포용)
  • my_package : import 이름 (Python 규칙)
  • src/ : 권장 구조 (setuptools / pip 공식 권장)
파일 및 폴더 명칭 설명
my-package 프로젝트 루트 디렉터리 - 하나의 Python 패키지 프로젝트 단위
- 빌드, 배포, 테스트 관련 모든 파일의 기준 위치
pyproject.toml 프로젝트 설정 파일
프로젝트 메타데이터
- 패키지 정의 파일
- 빌드 시스템, 프로젝트 메타데이터가 정의됨
- PEP 517/518/621 표준을 따름
README.md 프로젝트 설명 문서 - 패키지에 대한 설명 문서
- PyPI 나 devpi에 프로젝트 설명으로 표시됨
src/ 소스 루트 디렉터리 - 소스 코드를 분리하기 위한 src-layout 표준 구조
- import 경로 오염 방지를 위해 사용됨
src/my_package/ 패키지 디렉터리 - Import 대상이 되는 실제 Python 패키지
- 추후 import my_package 로 import 된다.
src/my_package/__init__.py 패키지 초기화 파일 - 해당 디렉터리를 Python 패키지로 인식케 함
- 공개 API를 정의하는 용도로도 사용됨
src/my_package/core.py 모듈(module) - 패키지의 핵심 로직을 담는 Python 모듈 파일
- 꼭 core.py 일 필요는 없으며, 알맞게 명명한다.
src/my_package/utils.py 보조 모듈 - 공통 함수, 유틸리티 로직을 담는 Python 모듈
- 꼭 utils.py 일 필요는 없으며, 알맞게 명명한다.
tests/ 테스트 디렉터리 - 단위 테스트 코드를 두는 위치
- 빌드 결과물에는 포함되지 않음

pyproject.toml 형식

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
[build-system]
requires = ["setuptools", "wheel"]
build-backend = "setuptools.build_meta"

[project]
name = "my-package"
version = "0.1.0"
description = "example package"
readme = "README.md"
requires-python = ">=3.8"
license = { text = "MIT" }
keywords = ["dummy", "my-package", "my", "package"]
authors = [{ name = "{author_name}", email = "{author_email}" }]
dependencies = [
	"requests>=2.28,<2.30",
	"pydantic>=2.0,<2.3"
]

[project.urls]
Documentation = "https://github.com/{some_id}/my-package#readme"
Issues = "https://github.com/{some_id}/my-package/issues"
Source = "https://github.com/{some_id}/my-package"

[tool.setuptools]
package-dir = {"" = "src"}

[tool.setuptools.packages.find]
where = ["src"]

  • build-system : 어떤 도구로 패키지를 빌드할지 정의
항목 명칭 설명
build-system 빌드 시스템 섹션 - 어떤 도구로 패키지를 빌드할지 정의하는 영역
requires 빌드 의존성 - 패키지를 빌드하기 위해 사전에 설치되어야 하는 패키지 목록
build-backend 빌드 백엔드 - 실제 빌드를 수행하는 Python 모듈


  • project : PEP 621 표준에 따른 프로젝트 정보 정의 영역
항목 명칭 설명
project 프로젝트 메타데이터 섹션 - PEP 621 표준에 따른 프로젝트 정보 정의 영역
name 프로젝트 이름 - 프로젝트의 배포용 이름
- PyPI나 devpi에서 보이는 프로젝트 식별자
version 버전 - 릴리즈(release)를 구분하는 버전 값
description 요약 설명 - 패키지에 대한 짧은 설명
readme 상세 설명 파일 - 프로젝트 설명으로 사용할 문서 파일
requires-python Python 버전 조건 - 이 패키지가 지원하는 Python 버전 범위
license 라이선스 - 프로젝트 라이선스 정보
keywords 키워드 - 검색용 키워드 목록
authors 작성자 정보 - 패키지 작성자 정보 (이름과 이메일)
dependencies 런타임 의존성 - 이 패키지 설치 시 함께 설치될 패키지


  • project.urls
항목 명칭 설명
Documentation 문서 사이트 -
Issues 소스 코드 저장소 -
Source 이슈 트래커 -


  • tools.setuptools : setuptools 설정 영역
항목 명칭 설명
tool.setuptools setuptools 설정 섹션 - setuptools 전용 확장 설정 영역
package-dir 패키지 루트 매핑 - 패키지 소스가 src/ 아래에 있음을 명시


  • tools.setuptools.packages.find : 패키지 자동 탐색 규칙
항목 명칭 설명
tool.setuptools.packages.find 패키지 자동 탐색 설정 - setuptools가 패키지를 자동으로 찾는 규칙을 정의
where 탐색 위치 - Python 패키지를 찾을 디렉터리 경로
- 여러 개를 지정할 수도 있다.

requires 와 dependencies 의 차이

  • requires : 빌드 의존성. 이 패키지를 빌드하기 위해 필요한 도구들. 즉 빌드 도구.
  • dependencies : 런타임 의존성. 이 라이브러리를 설치하는 사람에게 필요한 의존성.

(참고) build-backend 의 종류

  • build-backend : PEP 517 빌드 백엔드 모듈 경로
빌드 백엔드 build-backend 설명
setuptools setuptools.build_meta 가장 표준, 레거시 호환 최강
setuptools (no setup.py) setuptools.build_meta:__legacy__ setup.py 기반 레거시 프로젝트
flit flit_core.buildapi 단순 라이브러리용
poetry poetry.core.masonry.api poetry 전용
hatchling hatchling.build modern / 빠름
meson-python mesonpy C/C++ 확장
scikit-build-core scikit_build_core.build CMake 기반
maturin maturin Rust 확장

패키지 만들기 실습

프로젝트 만들기

(1) 디렉터리 생성

  • hatch는 프로젝트 디렉터리를 쉽게 만드는 데 도움을 주는 라이브러리이다.
1
2
3
4
5
# 설치
pip install hatch

# 프로젝트 생성
hatch new my-package
  • 디렉터리를 생성한 뒤, src 내의 디렉터리 명은 calc 로 변경하였다.
1
2
3
4
5
6
7
8
9
10
my-package
├── src
│   └── calc
│       ├── __about__.py
│       └── __init__.py
├── tests
│   └── __init__.py
├── LICENSE.txt
├── README.md
└── pyproject.toml

(2) pyproject.toml 작성

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
[build-system]
requires = ["setuptools", "wheel"]
build-backend = "setuptools.build_meta"

[project]
name = "my-package"
version = "0.1.0"
description = "example package"
readme = "README.md"
requires-python = ">=3.8"
license = "MIT"
keywords = ["dummy", "my-package", "my", "package"]
authors = [{ name = "tester", email = "tester@tester.com" }]
dependencies = [
	"pydantic>=2.0,<2.8"
]

[project.urls]
Documentation = "http://dummy-site/tester/my-package#readme"
Issues = "http://dummy-site/tester/my-package/issues"
Source = "http://dummy-site/tester/my-package"

[tool.setuptools]
package-dir = {"" = "src"}

[tool.setuptools.packages.find]
where = ["src"]

(3) 코드 작성

사칙연산을 하는 간단한 코드를 만들었다.

  • 연산자 데이터 모델
1
2
3
4
5
6
# src/calc/models.py
from pydantic import BaseModel

class Operands(BaseModel):
    a: int | float
    b: int | float
  • 연산 로직
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# src/calc/operations.py
from .models import Operands

def add(data: Operands) -> int | float:
    return data.a + data.b

def sub(data: Operands) -> int | float:
    return data.a - data.b

def mul(data: Operands) -> int | float:
    return data.a * data.b

def div(data: Operands) -> int | float:
    if data.b == 0:
        raise ZeroDivisionError("0으로 나눌 수 없습니다.")
    return data.a / data.b

  • 패키지 공개 API (__init__.py)
1
2
3
4
5
6
# src/calc/__init__.py
# 외부 사용자가 import 해서 사용하면 될 인터페이스를 명시한 것
from .models import Operands
from .operations import add, sub, mul, div

__all__ = ["Operands", "add", "sub", "mul", "div"] # from calc import * 시 노출되는 목록
  • 최종 디렉터리 구조
1
2
3
4
5
6
7
8
9
10
11
12
my-package
├── src
│   └── calc
│       ├── __about__.py
│       ├── __init__.py
│       ├── models.py
│       └── operations.py
├── tests
│   └── __init__.py
├── LICENSE.txt
├── README.md
└── pyproject.toml

릴리즈 파일 생성

  • 릴리즈 파일 생성을 위해 build 라이브러리 설치
1
pip install build
  • 빌드 수행
1
python -m build
1
2
3
4
5
6
7
8
9
10
11
# 출력
* Creating isolated environment: venv+pip...
* Installing packages in isolated environment:
  - setuptools
  - wheel
* Getting build dependencies for sdist...
...
* Building sdist...
* Building wheel...
...
Successfully built my_package-0.1.0.tar.gz and my_package-0.1.0-py3-none-any.whl
  • 빌드 결과
1
2
3
4
5
6
# 결과
my-package
├─ (다른 폴더 생략)
└─ dist/
    ├── my_package-0.1.0-py3-none-any.whl
    └── my_package-0.1.0.tar.gz

빌드 결과물을 설치하고 이용해보기

(1) 격리된 환경 준비

  • 빌드 결과물을 설치하고 이용해보기 위해 격리된 개발환경을 준비한다.
  • 패키지를 빌드한 디렉터리와는 별개의 디렉터리에서, uv 로 프로젝트를 생성한다.
1
uv init my-package-test
1
2
3
4
5
6
# 결과
my-package-test
├─ .python-version
├─ main.py
├─ pyproject.toml
└─ README.md

(2) 패키지 설치

  • lib 디렉터리를 만들고, 만들어둔 calc 패키지의 빌드파일을 복사해넣는다.

(참고) PyPI 서버나 devpi 서버에 업로드된 패키지는 pip install 등으로 바로 설치할 수 있다.
이 내용은 devpi-server 관련 포스팅을 확인하기 바란다. 링크

1
2
3
4
5
6
7
my-package-test
├─ lib
│   └─ my_package-0.1.0-py3-none-any.whl
├─ .python-version
├─ main.py
├─ pyproject.toml
└─ README.md
  • 패키지를 설치한다.
1
uv add lib/my_package-0.1.0-py3-none-any.whl
1
2
3
4
5
6
7
8
9
10
# 결과
...
Resolved 7 packages in 255ms
Installed 6 packages in 15ms
 + annotated-types==0.7.0
 + my-package==0.1.0 (from file:...lib/my_package-0.1.0-py3-none-any.whl)
 + pydantic==2.12.5
 + pydantic-core==2.41.5
 + typing-extensions==4.15.0
 + typing-inspection==0.4.2

(3) 패키지 import 해서 사용

  • 코드
1
2
3
4
5
6
7
8
9
10
11
12
from calc import Operands, add, sub, mul, div

def main():
    a = 10
    b = 2
    print(add(Operands(a=a, b=b)))
    print(sub(Operands(a=a, b=b)))
    print(mul(Operands(a=a, b=b)))
    print(div(Operands(a=a, b=b)))

if __name__ == "__main__":
    main()
  • 결과
1
uv run main.py
1
2
3
4
12
8
20
5.0

(참고) 자동완성

  • 앞서서 패키지를 만들 때 __init__.py 에 인터페이스를 정의해뒀다.
  • 그 덕분에 패키지를 import 해 사용할 때, 아래처럼 자동완성이 가능해진다.

Reference

PEP 517 – A build-system independent format for source trees
PEP 518 – Specifying Minimum Build System Requirements for Python Projects
PEP 621 – Storing project metadata in pyproject.toml
https://github.com/pypa/hatch

Comments