pyproject.toml и src-layout: структура Python-проекта

Файл pyproject.toml хранит стандартные метаданные проекта и сообщает инструментам, какой build backend использовать. Раскладка src помещает импортируемый пакет не рядом с конфигурацией, а в отдельный каталог src. Вместе эти решения отделяют исходный код пакета от служебных файлов репозитория и заставляют разработчика проверять установленный проект, а не случайную копию из текущего каталога.

Минимальное дерево проекта

Для небольшого консольного приложения структура может выглядеть так:

word-counter/
├── pyproject.toml
├── README.md
├── src/
│   └── word_counter/
│       ├── __init__.py
│       └── cli.py
└── tests/

Имя дистрибутива в метаданных может содержать дефис, а импортируемый пакет обычно использует подчёркивание. Это разные пространства имён: пользователь устанавливает дистрибутив word-counter-demo, а код выполняет import word_counter.

Что описывает pyproject.toml

Секция build-system задаёт инструменты, необходимые для сборки, и backend. Секция project содержит имя, версию, требуемую версию Python и другие метаданные. Настройки конкретного backend располагаются в его собственной секции.

[build-system]
requires = ["setuptools"]
build-backend = "setuptools.build_meta"

[project]
name = "word-counter-demo"
version = "0.1.0"
description = "Учебный счётчик слов"
readme = "README.md"
requires-python = ">=3.10"

[project.scripts]
word-count = "word_counter.cli:main"

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

Таблица project.scripts объявляет консольную точку входа. После установки инструмент создаёт команду word-count, которая импортирует функцию main; сам TOML не выполняет Python-код.

Установка src-layout в режиме разработки

Пусть src/word_counter/cli.py содержит рабочий минимальный пример:

def count_words(text):
    return len(text.split())


def main():
    sample = "ясная структура проекта"
    print(f"words={count_words(sample)}")

В активном окружении из корня проекта выполните:

python -m pip install -e .
word-count
python -c "import word_counter; print(word_counter.__file__)"

Ожидаемый первый результат — words=3, а затем путь внутри src/word_counter. Editable-установка связывает окружение с рабочими исходниками, поэтому изменения видны без создания нового wheel. Она всё равно является установкой и должна выполняться в выбранной .venv.

Зачем отделять src

При плоской раскладке корень репозитория часто попадает в путь поиска, и импорт может пройти даже тогда, когда пакет не включён в сборку или забыта нужная конфигурация. В src-layout команда из корня не находит пакет только по соседству; требуется корректная установка. Это раньше обнаруживает различия между локальной разработкой и тем, что получит пользователь.

Каталоги tests, документация и конфигурация не становятся подмодулями приложения случайно. Однако src не исправляет неверные метаданные автоматически: нужно проверить, какие файлы вошли в wheel, и прогнать тесты на собранном артефакте.

Как диагностировать сломанный src-layout

Если до установки команда отвечает ModuleNotFoundError: No module named 'word_counter', для src-layout это ожидаемый сигнал, а не повод добавить src в sys.path внутри программы. Проверьте активный Python, выполните python -m pip install -e . и затем python -m pip show word-counter-demo. Если установка прошла, но пакет всё ещё отсутствует, исследуйте секцию поиска пакетов и реальное имя каталога. Ошибка создания команды требует дополнительно проверить строку word_counter.cli:main: модуль должен импортироваться, а объект main существовать.

Собираем и устанавливаем учебный пакет

Соберите указанное дерево с функцией count_words, установите проект editable-режимом и вызовите консольную команду из каталога вне репозитория. Добавьте тест, который импортирует функцию по публичному имени. Для самостоятельной проверки временно измените where = ["src"] на несуществующий каталог, переустановите проект и объясните результат диагностики; затем восстановите конфигурацию и убедитесь, что импорт указывает на рабочий src.

Проверка структуры перед сборкой

Вопросы о pyproject.toml и src-layout

pyproject.toml заменяет requirements.txt?

Не всегда. Метаданные проекта описывают зависимости устанавливаемого дистрибутива, а requirements-файл может фиксировать целое окружение приложения или инструменты разработки. Их роли пересекаются, но не тождественны.

Обязательно ли применять src-layout?

Нет, Python поддерживает и плоскую структуру. src полезен, когда важно рано выявлять неявные импорты из корня и чётко отделять пакет от остальных файлов репозитория.

Нужно ли добавлять src в переменную PYTHONPATH?

Для обычного процесса разработки лучше установить проект в виртуальное окружение. Постоянный ручной PYTHONPATH скрывает ошибки конфигурации сборки и делает запуск зависимым от внешнего состояния shell.

Источники