Публикация пакета в PyPI: от кода до pip install
Пошаговый гайд: как собрать проект с pyproject.toml, проверить сборку на TestPyPI и выложить пакет в PyPI так, чтобы его можно было поставить через pip install.

К концу этого гайда ваш код будет лежать на pypi.org, и любой сможет поставить его командой pip install ваш-пакет. Первая публикация с нуля занимает около часа: большую часть времени съедает не сборка, а регистрация аккаунта, настройка двухфакторной аутентификации и генерация API-токена. Сама сборка и загрузка — три команды.
Гайд написан под актуальные на 2024 год инструменты: сборка через build, загрузка через twine, метаданные в pyproject.toml по стандарту PEP 621. Старый способ через setup.py sdist upload не используем — прямая загрузка через setup.py давно не рекомендуется.
Что нужно иметь до начала
- Python 3.8 или новее (
python --version). - Аккаунт на pypi.org и отдельный аккаунт на test.pypi.org — это две разные площадки с разными базами пользователей.
- Включённая двухфакторная аутентификация на PyPI — с 2024 года она обязательна для загрузки пакетов.
- API-токен вместо логина и пароля: PyPI больше не принимает загрузку по паролю.
- Уникальное имя пакета. Проверьте заранее: откройте
https://pypi.org/project/имя/— если страница есть, имя занято, придумывайте другое.
Установите инструменты сборки в текущее окружение:
python -m pip install --upgrade build twineПроверка: python -m build --version и python -m twine --version должны вывести номера версий без ошибок.
Шаг 1. Разложите проект по структуре src-layout
Соберите файлы в такую структуру. src-layout избавляет от классической ошибки, когда тесты случайно импортируют пакет из корня проекта, а не из установленной сборки.
my-cool-lib/
├── src/
│ └── my_cool_lib/
│ ├── __init__.py
│ └── core.py
├── tests/
│ └── test_core.py
├── README.md
├── LICENSE
└── pyproject.tomlОбратите внимание: имя каталога с кодом (my_cool_lib) пишется через подчёркивания — это имя для import. Имя проекта на PyPI (my-cool-lib) может быть через дефис. PyPI считает дефис и подчёркивание в имени проекта эквивалентными.
Проверка: файл src/my_cool_lib/__init__.py существует, даже если пустой. Без него каталог не считается пакетом.
Шаг 2. Опишите метаданные в pyproject.toml
Это единственный обязательный файл конфигурации. Ниже — рабочий минимум для сборки через backend setuptools.
[build-system]
requires = ["setuptools>=61.0"]
build-backend = "setuptools.build_meta"
[project]
name = "my-cool-lib"
version = "0.1.0"
description = "Короткое описание в одну строку"
readme = "README.md"
requires-python = ">=3.8"
license = { text = "MIT" }
authors = [
{ name = "Имя Фамилия", email = "you@example.com" }
]
dependencies = [
"requests>=2.28",
]
[project.urls]
Homepage = "https://github.com/username/my-cool-lib"
[tool.setuptools.packages.find]
where = ["src"]Секция [tool.setuptools.packages.find] с where = ["src"] обязательна для src-layout — без неё setuptools не найдёт код и соберёт пустой пакет.
Проверка: version в файле совпадает с версией, которую вы собираетесь выпустить. PyPI не даёт перезаписать уже загруженную версию — повторная загрузка того же номера завершится ошибкой.
Про версию: одна и та же дважды не пройдёт
Если вы загрузили 0.1.0 и нашли опечатку через минуту, поднять исправление под тем же номером нельзя даже после удаления релиза. Придётся выпускать 0.1.1. Поэтому и существует TestPyPI — там же вы отловите ошибки до боевой загрузки.
Шаг 3. Соберите дистрибутивы
Из корня проекта (там, где лежит pyproject.toml):
python -m buildКоманда создаст каталог dist/ с двумя файлами:
my_cool_lib-0.1.0.tar.gz— исходный дистрибутив (sdist).my_cool_lib-0.1.0-py3-none-any.whl— wheel, из него pip ставит быстрее.
Проверка: ls dist/ показывает оба файла. Если .whl не появился — проверьте, что build установлен именно в активном окружении.
Прогоните проверку метаданных перед загрузкой:
python -m twine check dist/*На каждый файл должно вывести PASSED. Частая причина FAILED — некорректный README, который не рендерится как разметка.
Шаг 4. Обкатайте загрузку на TestPyPI
TestPyPI — отдельная копия репозитория для тренировки. Сначала создайте там API-токен: аккаунт → Account settings → API tokens → Add API token. Токен показывается один раз, начинается с pypi-.
Загрузите сборку на тестовый индекс:
python -m twine upload --repository testpypi dist/*При запросе имени пользователя введите __token__ (два подчёркивания с обеих сторон), в поле пароля вставьте токен целиком вместе с префиксом pypi-.
Теперь поставьте пакет из TestPyPI в чистое окружение и проверьте, что он импортируется. Зависимости берём с обычного PyPI, потому что их может не быть на тестовом:
python -m pip install \
--index-url https://test.pypi.org/simple/ \
--extra-index-url https://pypi.org/simple/ \
my-cool-libПроверка: python -c "import my_cool_lib; print(my_cool_lib.__file__)" отрабатывает без ошибок и печатает путь внутри site-packages.
Шаг 5. Загрузите на боевой PyPI
Когда установка с TestPyPI прошла чисто, создайте отдельный токен уже на pypi.org (токены между площадками не взаимозаменяемы) и загрузите:
python -m twine upload dist/*Снова __token__ как логин и токен как пароль. Через пару минут страница https://pypi.org/project/my-cool-lib/ станет доступна, а команда сработает на любой машине:
pip install my-cool-libЗагруженную версию нельзя перезаписать, но можно «спрятать» (yank) — pip перестанет ставить её по умолчанию, а те, у кого она уже стоит в lock-файле, продолжат получать. Это не удаление, а пометка «не для новых установок».
Чтобы не вводить токен каждый раз
Токен можно сохранить в файле ~/.pypirc. Права на файл выставьте 600, чтобы токен не читали другие пользователи системы.
[pypi]
username = __token__
password = pypi-YOUR_TOKEN_HERE
[testpypi]
username = __token__
password = pypi-YOUR_TEST_TOKEN_HEREЕсли не получилось
HTTPError: 403 Forbiddenпри загрузке. Чаще всего в поле username введён логин вместо__token__, либо токен от TestPyPI использован на боевом PyPI. Проверьте, что логин — ровно__token__, а токен создан на нужной площадке.File already exists. Версия с таким номером уже загружена. Поднимитеversionвpyproject.toml, пересоберите (rm -rf dist/ && python -m build) и загружайте снова.The name '...' is too similar to an existing project. PyPI отклоняет имена, слишком близкие к существующим. Придумайте более отличающееся имя.- Пакет ставится, но
importпадает сModuleNotFoundError. Обычно забыта секция[tool.setuptools.packages.find]сwhere = ["src"], и в wheel не попал код. Распакуйте.whl(это zip) и убедитесь, что каталог с кодом внутри есть. twine checkвыдаётFAILEDна README. Укажите тип разметки явно, если README не в reStructuredText: setuptools определяет формат по расширению.mdавтоматически, но при странном содержимом рендер может падать — упростите README и повторите проверку.
Что делать дальше
Для повторяющихся релизов настройте выпуск через GitHub Actions с механизмом Trusted Publishing (OIDC) — тогда токены в репозитории вообще не нужны, PyPI доверяет подписанному запросу от вашего workflow. Следующий шаг после первого релиза — привести версионирование к схеме semver и завести CHANGELOG, чтобы пользователи понимали, что меняется между 0.1.0 и 0.2.0. Точный синтаксис секций pyproject.toml и список поддерживаемых полей сверяйте с официальной спецификацией упаковки, она обновляется.
Таможенный брокер (ВЭД): Коды ТН ВЭД, маркировка «Честный ЗНАК», сертификация.
Спросить за 20 ₽Источники: Packaging Python Projects — Python Packaging User Guide, PyPI Help — API tokens and Trusted Publishing
Частые вопросы
А если у меня старый проект с setup.py — обязательно переходить на pyproject.toml?
Можно ли удалить пакет с PyPI, если я передумал?
Не сработала команда twine upload с ошибкой 403 — что не так?
Обязательно ли включать двухфакторную аутентификацию?
Зачем нужен TestPyPI, если можно сразу грузить на боевой?
Что попадёт в дистрибутив, а что нет — как проверить?
Материал носит информационный характер и подготовлен редакцией «Агентуры». Он не является офертой, рекламой или индивидуальной консультацией. Упомянутые продукты, компании и торговые знаки принадлежат их правообладателям. Перед принятием решений, влекущих юридические или финансовые последствия, обратитесь к профильному специалисту.