|

pyproject.toml은 왜 중요할까: Python 프로젝트 설정이 한 파일로 모이는 이유

pyproject.toml Python 프로젝트 설정 대표 이미지
pyproject.toml은 Python 프로젝트의 설명서이자 도구 설정의 출발점이다

pyproject.toml은 요즘 Python 프로젝트에서 거의 출발점처럼 등장합니다. 그런데 처음 보면 이 파일이 패키징 파일인지, 의존성 파일인지, 도구 설정 파일인지 헷갈립니다.

정답은 하나로 고정되어 있지 않습니다. pyproject.toml은 프로젝트 이름, Python 버전, 의존성, 빌드 방식, 그리고 ruff나 pytest 같은 도구 설정이 모이는 기준 파일입니다.

이 글은 pyproject.toml 문법을 전부 외우는 글이 아닙니다. Python 프로젝트를 만들 때 이 파일이 왜 중요해졌고, 어디까지 넣는 것이 자연스러운지 단계적으로 정리합니다.


pyproject.toml을 먼저 한 장으로 보기

pyproject.toml Python 프로젝트 설정 요약 카드
pyproject.toml은 Python 프로젝트의 메타데이터와 도구 설정을 한곳에 모으는 기준 파일이다

pyproject.toml은 프로젝트 설명서에 가깝다

pyproject.toml을 가장 쉽게 이해하는 방법은 프로젝트의 설명서로 보는 것입니다. 프로젝트 이름이 무엇인지, 어떤 Python 버전을 요구하는지, 어떤 의존성이 필요한지, 어떤 방식으로 빌드되는지를 한 파일에서 읽을 수 있습니다.

예전에는 setup.py, setup.cfg, requirements.txt, tox.ini, pytest.ini, .flake8 같은 파일이 흩어져 있는 경우가 많았습니다. 지금도 이런 파일을 쓸 수 있지만, 많은 도구가 pyproject.toml을 함께 지원하면서 설정의 중심이 옮겨졌습니다.

[project]
name = "example-app"
version = "0.1.0"
requires-python = ">=3.12"
dependencies = [
  "httpx>=0.28",
]

이 정도만 있어도 프로젝트 이름, 버전, Python 요구 버전, 실행에 필요한 의존성을 사람이 읽을 수 있습니다. 중요한 것은 명령어보다 프로젝트의 기준이 파일에 남는다는 점입니다.


requires-python은 생각보다 중요하다

uv 문서는 project.requires-python 값을 설정하는 것을 권장합니다. 이 값은 단순한 문서가 아니라, 프로젝트에서 허용되는 Python 문법과 의존성 선택에 영향을 줍니다.

예를 들어 `>=3.12`라고 적으면 팀원과 CI는 이 프로젝트가 Python 3.12 이상을 기준으로 작성된다는 사실을 알 수 있습니다. 반대로 이 값을 비워두면 도구가 어떤 Python 버전을 기준으로 판단해야 할지 애매해집니다.

requires-python은 “내 컴퓨터에서는 돌아간다”를 줄이기 위한 최소한의 프로젝트 계약입니다.


uv를 쓰면 pyproject.toml의 역할이 더 커진다

uv는 단순히 패키지를 빨리 설치하는 도구로만 보기 어렵습니다. 프로젝트 생성, 의존성 추가, lock, sync, 실행 흐름이 pyproject.toml과 연결됩니다.

uv init
uv add httpx
uv add --dev pytest ruff
uv run pytest

이 흐름에서 pyproject.toml은 사람이 직접 편집하는 파일이면서, uv가 프로젝트 상태를 이해하는 파일이 됩니다. 그래서 패키지를 하나 설치할 때도 프로젝트 기준이 함께 갱신됩니다.


도구 설정도 한곳에 모을 수 있다

Ruff는 pyproject.toml, ruff.toml, .ruff.toml을 설정 파일로 지원합니다. pytest, mypy 같은 도구도 pyproject.toml 설정을 지원합니다. 모든 설정을 반드시 한 파일에 넣어야 하는 것은 아니지만, 작은 프로젝트에서는 한곳에 모으면 흐름이 단순해집니다.

[tool.ruff]
line-length = 100
target-version = "py312"

[tool.ruff.lint]
select = ["E", "F", "I", "UP", "B"]

[tool.pytest.ini_options]
testpaths = ["tests"]
addopts = ["-ra", "-q"]

여기서 중요한 것은 도구별 설정 이름이 다르다는 점입니다. `[project]`는 표준 프로젝트 메타데이터에 가깝고, `[tool.ruff]`나 `[tool.pytest.ini_options]`는 각 도구가 읽는 영역입니다.


패키지와 애플리케이션은 다르게 생각해야 한다

모든 Python 프로젝트가 PyPI에 배포되는 패키지는 아닙니다. 내부 자동화 스크립트, FastAPI 앱, 데이터 처리 앱처럼 배포 방식이 다른 프로젝트도 많습니다.

uv 문서는 build-system이 있으면 현재 프로젝트 자체를 빌드하고 설치할 대상으로 볼 수 있다고 설명합니다. 반대로 단순 스크립트나 앱이라면 build-system 없이 의존성 관리 중심으로 시작해도 됩니다.

즉 pyproject.toml을 쓴다고 해서 곧바로 패키징 전문가가 되어야 하는 것은 아닙니다. 먼저 프로젝트 이름, Python 버전, 의존성, 개발 도구 설정부터 안정적으로 관리하면 됩니다.


처음 프로젝트를 만들 때 추천하는 최소 구조

example-app/
  pyproject.toml
  src/
    example_app/
      __init__.py
  tests/
    test_example.py
  README.md

라이브러리로 배포할 가능성이 있거나 테스트를 분리하고 싶다면 `src` 구조가 도움이 됩니다. 단순한 개인 스크립트라면 처음부터 이 구조를 강제할 필요는 없습니다.

  • 팀 프로젝트: requires-python을 반드시 적는다
  • 테스트가 있다면 pytest 설정 위치를 정한다
  • lint/format 도구는 ruff 기준을 먼저 통일한다
  • 배포할 패키지라면 build-system과 project metadata를 더 엄격하게 관리한다

정리

pyproject.toml은 Python 프로젝트의 모든 것을 마법처럼 해결하는 파일이 아닙니다. 하지만 프로젝트 기준을 한곳에 모으는 파일이기 때문에, 팀원과 도구가 같은 정보를 보게 만드는 데 큰 역할을 합니다.

처음에는 최소한의 `[project]` 정보와 `requires-python`부터 시작하면 충분합니다. 그 다음 uv, ruff, pytest 설정을 조금씩 옮기면 프로젝트가 어떤 기준으로 동작하는지 훨씬 읽기 쉬워집니다.

함께 보면 좋은 글: uv는 pip, poetry와 무엇이 다를까, Python free-threading은 GIL을 없앤 걸까

출처: Python Packaging User Guide, uv project configuration, Ruff configuration

함께보면 좋은 글