Django-Admin.ru

Как создать свой пакет на Python и опубликовать в PyPI

Создание библиотеки Python: src-структура, pyproject.toml, выбор бэкенда, pip install -e, python -m build, TestPyPI, twine, uv и Trusted Publishing.

Редакция Django-Admin.ru
· 19 мин чтения

Содержание · 14 разделов
  1. Что понадобится
  2. Структура проекта: src-layout
  3. Имя проекта и имя пакета
  4. Код библиотеки
  5. README и лицензия
  6. Файл pyproject.toml
  7. Выбор бэкенда сборки
  8. Метаданные проекта
  9. Зависимости
  10. Консольная команда
  11. Установка в режиме разработки: pip install -e
  12. Сборка: python -m build
  13. Проверка перед публикацией
  14. Если в пакете есть данные
  15. Публикация в TestPyPI
  16. Публикация в PyPI
  17. Сборка и публикация через uv
  18. Trusted Publishing: публикация из CI без токенов
  19. Версии и выпуск обновлений
  20. Частые ошибки при публикации
  21. Частые вопросы
  22. Можно ли пользоваться библиотекой, не публикуя её в PyPI?
  23. Как сделать приватный пакет?
  24. Нужен ли ещё setup.py?
  25. Как удалить пакет с PyPI?
  26. Подойдёт ли Poetry вместо этой связки?
  27. Как подключить свой пакет к другому проекту, пока он в разработке?
  28. Итог

Чтобы создать свой пакет на Python и опубликовать его в PyPI, разложите код по src-структуре, опишите проект в файле pyproject.toml, соберите дистрибутивы командой python -m build, проверьте их и загрузите через twine — сначала на тестовый сервер TestPyPI, потом на настоящий PyPI. После этого библиотеку сможет поставить кто угодно командой pip install:

python -m pip install --upgrade build twine
python -m build                                   # → dist/*.whl и dist/*.tar.gz
python -m twine check dist/*                      # проверка метаданных
python -m twine upload --repository testpypi dist/*
python -m twine upload dist/*                     # публикация в PyPI

Ниже весь путь разобран на учебной библиотеке pluralize-ru, которая склоняет слова после чисел («1 файл», «2 файла», «5 файлов»). Пакет собран и проверен на Python 3.12 с build 1.6, twine 7.0 и hatchling 1.32. Если вы ещё путаетесь в терминах «модуль», «пакет» и «библиотека», начните с обзора библиотек Python.

Что понадобится

  • Python 3.10 или новее и виртуальное окружение для проекта.
  • Аккаунты на pypi.org и test.pypi.org. Это два независимых сервера: логин, пароль и токены у них разные. Двухфакторная аутентификация на PyPI обязательна — без неё загрузить пакет не получится.
  • Свободное имя проекта. Откройте https://pypi.org/project/ваше-имя/: если страница отвечает 404, имя не занято.

Создайте и активируйте окружение в папке проекта:

# Windows
py -m venv .venv
.venv\Scripts\activate
# macOS / Linux
python3 -m venv .venv
source .venv/bin/activate

В активированном окружении команда python одинаково работает во всех системах, поэтому дальше примеры используют её.

Структура проекта: src-layout

Рекомендуемая раскладка — src-layout: импортируемый код лежит во вложенной папке src/, а в корне остаются служебные файлы.

pluralize-ru/
├── LICENSE
├── README.md
├── pyproject.toml
├── src/
│   └── pluralize_ru/
│       ├── __init__.py
│       ├── cli.py
│       └── core.py
└── tests/
    └── test_core.py

Альтернатива — flat-layout, когда папка pluralize_ru/ лежит прямо в корне. Она проще, но у неё есть ловушка: Python добавляет текущую папку в начало пути импорта, поэтому тесты и скрипты, запущенные из корня, импортируют исходники напрямую, а не установленный пакет. Ошибка в конфигурации (забытый модуль, не попавший в сборку файл) остаётся незамеченной до первой жалобы пользователя. В src-layout код импортируется только после установки, так что вы тестируете ровно то, что получат пользователи.

Имя проекта и имя пакета

У библиотеки два имени:

  • имя дистрибутива — pluralize-ru, его пишут в pip install и в поле name файла pyproject.toml. Допустимы латинские буквы, цифры, -, _ и .;
  • имя пакета для импорта — pluralize_ru, это название папки внутри src/. Дефис в имени модуля недопустим, поэтому его меняют на подчёркивание.

PyPI сравнивает имена без учёта регистра и считает -, _ и . одинаковыми: Pluralize_RU и pluralize-ru — один проект. Слишком похожие на существующие имена PyPI тоже отклоняет, так что проверяйте уникальность до того, как переименовывать папки.

Код библиотеки

Логика — в src/pluralize_ru/core.py:

def plural(n: int, one: str, few: str, many: str) -> str:
    """Возвращает форму слова для числа n: plural(5, "файл", "файла", "файлов")."""
    n = abs(n)
    if n % 10 == 1 and n % 100 != 11:
        return one
    if 2 <= n % 10 <= 4 and not 12 <= n % 100 <= 14:
        return few
    return many

В __init__.py определяется публичный интерфейс: что пользователь получит после import pluralize_ru. Версию удобно не дублировать в коде, а читать из метаданных установленного пакета:

"""Склонение русских слов после числительных."""

from importlib.metadata import version

from .core import plural

__all__ = ["plural"]
__version__ = version("pluralize-ru")

Файл __init__.py можно оставить пустым, но создать его стоит: с ним папка импортируется как обычный пакет.

Консольная команда — в cli.py. Функция main() возвращает код завершения:

import sys

from .core import plural


def main() -> int:
    if len(sys.argv) != 5:
        print("Использование: pluralize-ru ЧИСЛО ОДИН ДВА ПЯТЬ")
        return 1
    n = int(sys.argv[1])
    print(n, plural(n, *sys.argv[2:]))
    return 0

README и лицензия

README.md станет страницей проекта на PyPI: опишите, что делает библиотека, как её установить, и дайте короткий пример. Файл LICENSE содержит полный текст лицензии. Без лицензии пользователи формально не имеют права использовать код; для открытых библиотек чаще всего выбирают MIT, Apache-2.0 или BSD-3-Clause.

Файл pyproject.toml

pyproject.toml — единый файл конфигурации проекта. В нём три таблицы:

  • [build-system] — каким инструментом собирать пакет;
  • [project] — метаданные: имя, версия, зависимости, авторы;
  • [tool.*] — настройки отдельных инструментов ([tool.hatch], [tool.pytest.ini_options], [tool.ruff]).

Выбор бэкенда сборки

Команды pip и build сами не превращают исходники в архив — эту работу делает бэкенд сборки (build backend), указанный в [build-system]. Для чистого Python все популярные бэкенды понимают одинаковую таблицу [project], поэтому выбор влияет в основном на дополнительные настройки и на то, какие файлы попадут в архив.

БэкендСтрока requiresbuild-backendКогда выбирать
Hatchling"hatchling >= 1.26""hatchling.build"вариант по умолчанию для чистого Python; сам находит пакет в src/ и включает все его файлы
setuptools"setuptools >= 77.0.3""setuptools.build_meta"существующие проекты с setup.py, расширения на C; нефайлы .py нужно перечислять явно
Flit"flit_core >= 3.12.0, <5""flit_core.buildapi"маленькие библиотеки на чистом Python, минимум настроек
uv_build"uv_build >= 0.13.0, <0.14.0""uv_build"вы управляете проектом через uv; только чистый Python
pdm-backend, poetry-coreсм. документацию PDM и Poetry—проект ведётся в PDM или Poetry

Учебный проект собран всеми четырьмя первыми бэкендами без изменений в [project] — отличались только две строки [build-system]. Если сомневаетесь, берите Hatchling: его использует официальное руководство Python Packaging User Guide. Для uv_build диапазон версий привязан к версии uv: команда uv init --lib подставляет подходящий сама.

Метаданные проекта

Полный pyproject.toml учебной библиотеки:

[build-system]
requires = ["hatchling >= 1.26"]
build-backend = "hatchling.build"

[project]
name = "pluralize-ru"
version = "0.1.0"
description = "Склонение русских слов после числительных: 1 файл, 2 файла, 5 файлов"
readme = "README.md"
requires-python = ">=3.10"
license = "MIT"
license-files = ["LICEN[CS]E*"]
authors = [
  { name = "Иван Петров", email = "ivan@example.com" },
]
keywords = ["russian", "plural", "i18n"]
classifiers = [
  "Programming Language :: Python :: 3",
  "Operating System :: OS Independent",
  "Natural Language :: Russian",
]
dependencies = []

[project.optional-dependencies]
dev = ["pytest"]

[project.scripts]
pluralize-ru = "pluralize_ru.cli:main"

[project.urls]
Homepage = "https://github.com/ivan/pluralize-ru"
Issues = "https://github.com/ivan/pluralize-ru/issues"

Что означают поля:

  • name — имя дистрибутива на PyPI, единственное поле, которое нельзя вычислять динамически;
  • version — версия выпуска, подробнее в разделе о версиях ниже;
  • description — одна строка, она отображается в результатах поиска PyPI;
  • readme — файл с подробным описанием; формат определяется по расширению (.md или .rst);
  • requires-python — минимальная версия Python. pip не установит пакет на более старый интерпретатор и подберёт подходящий старый выпуск, если он есть;
  • license — SPDX-идентификатор лицензии ("MIT", "Apache-2.0", "GPL-3.0-or-later"), license-files — шаблоны файлов с текстом лицензии;
  • classifiers — метки для поиска и фильтров на PyPI. На установку они не влияют: ограничивает версии Python только requires-python;
  • [project.urls] — ссылки в боковой панели страницы проекта.

Важно: старая запись license = {text = "MIT"} устарела. Если сборка падает с ошибкой, что license должен быть таблицей, ваш бэкенд слишком старый — поднимите минимальную версию в requires.

Зависимости

Библиотеки, без которых ваш код не работает, перечисляются в dependencies. Для библиотеки указывайте диапазоны, а не точные версии:

dependencies = [
  "requests>=2.28",
  "rich>=13,<16",
]

Жёсткое requests==2.32.3 сломает установку у любого пользователя, которому нужна другая версия requests. Точные версии уместны в requirements.txt конкретного приложения, а не в метаданных библиотеки.

Необязательные зависимости группируются в [project.optional-dependencies]: группа dev из примера ставится командой pip install "pluralize-ru[dev]". Так же оформляют дополнительные возможности, например pip install "mylib[excel]".

Консольная команда

Таблица [project.scripts] создаёт исполняемую команду при установке пакета. Запись pluralize-ru = "pluralize_ru.cli:main" означает: команда pluralize-ru импортирует функцию main из модуля pluralize_ru.cli, вызывает её и передаёт результат в sys.exit(). На Windows pip создаст pluralize-ru.exe в папке Scripts окружения.

Установка в режиме разработки: pip install -e

Пока вы пишете библиотеку, установите её в окружение в редактируемом режиме (editable install). Пакет регистрируется в окружении, но указывает на вашу папку src/, поэтому правки в .py-файлах видны сразу, без переустановки:

python -m pip install -e ".[dev]"
Successfully installed pluralize-ru-0.1.0

Кавычки вокруг .[dev] нужны для zsh и не мешают в других оболочках. Проверка:

python -c "import pluralize_ru; print(pluralize_ru.__version__)"   # → 0.1.0
pluralize-ru 3 яблоко яблока яблок                                  # → 3 яблока

Тесты в tests/test_core.py импортируют установленный пакет:

import pytest

from pluralize_ru import plural


@pytest.mark.parametrize(
    "n, expected",
    [(1, "файл"), (2, "файла"), (5, "файлов"), (11, "файлов"), (21, "файл"), (114, "файлов")],
)
def test_plural(n, expected):
    assert plural(n, "файл", "файла", "файлов") == expected
$ python -m pytest -q
......                                                                   [100%]
6 passed in 0.03s

Подробнее о тестах — в статье про pytest.

Совет: переустанавливайте пакет (pip install -e . ещё раз), когда меняете pyproject.toml: новые зависимости, консольные команды и версия в метаданных подхватываются только при установке.

Сборка: python -m build

Перед сборкой удалите старую папку dist/, чтобы в загрузку не попали архивы прошлых версий. Затем выполните из папки с pyproject.toml:

python -m pip install --upgrade build
python -m build
* Creating isolated environment: venv+pip...
* Installing packages in isolated environment:
  - hatchling >= 1.26
...
Successfully built pluralize_ru-0.1.0.tar.gz and pluralize_ru-0.1.0-py3-none-any.whl

build создаёт временное изолированное окружение, ставит туда бэкенд из [build-system] и собирает два файла:

  • sdist (.tar.gz, source distribution) — исходники с pyproject.toml; из него пакет можно пересобрать;
  • wheel (.whl, built distribution) — готовый к установке архив. pip предпочитает wheel: установка из него быстрее и не требует сборки.

Публикуйте оба файла. Тег py3-none-any в имени wheel означает «любой Python 3, любая платформа» — так выглядит сборка библиотеки на чистом Python. В именах файлов дефис заменён на подчёркивание: так стандарт нормализует имя дистрибутива.

Важно: команды python setup.py sdist bdist_wheel и python setup.py upload из старых инструкций устарели. Используйте python -m build и twine.

Проверка перед публикацией

Загруженный файл на PyPI нельзя заменить, поэтому проверьте сборку локально.

Сначала метаданные и README — twine проверит, что описание отобразится на PyPI:

python -m pip install --upgrade twine
python -m twine check dist/*
Checking dist/pluralize_ru-0.1.0-py3-none-any.whl: PASSED
Checking dist/pluralize_ru-0.1.0.tar.gz: PASSED

Затем содержимое wheel: внутри должны быть все модули и данные пакета, но не должно быть секретов, локальных конфигов и тестовых выгрузок.

python -m zipfile -l dist/pluralize_ru-0.1.0-py3-none-any.whl
File Name                                             Modified             Size
pluralize_ru/__init__.py                       2020-02-02 00:00:00          212
pluralize_ru/cli.py                            2020-02-02 00:00:00          273
pluralize_ru/core.py                           2020-02-02 00:00:00          345
pluralize_ru-0.1.0.dist-info/METADATA          2020-02-02 00:00:00          914
pluralize_ru-0.1.0.dist-info/WHEEL             2020-02-02 00:00:00           87
pluralize_ru-0.1.0.dist-info/entry_points.txt  2020-02-02 00:00:00           55
pluralize_ru-0.1.0.dist-info/licenses/LICENSE  2020-02-02 00:00:00           77
pluralize_ru-0.1.0.dist-info/RECORD            2020-02-02 00:00:00          651

Фиксированная дата 2020-02-02 — особенность Hatchling для воспроизводимых сборок. Содержимое sdist смотрят командой tar tzf dist/pluralize_ru-0.1.0.tar.gz.

Наконец, установите wheel в чистое окружение и запустите пакет из другой папки — так проверяется именно собранный архив:

python -m venv /tmp/check-env
/tmp/check-env/bin/python -m pip install dist/pluralize_ru-0.1.0-py3-none-any.whl
cd /tmp && /tmp/check-env/bin/pluralize-ru 22 яблоко яблока яблок   # → 22 яблока

На Windows путь к интерпретатору окружения — check-env\Scripts\python.exe.

Если в пакете есть данные

JSON-словари, шаблоны и другие не-.py файлы кладите внутрь папки пакета (src/pluralize_ru/words.json) и читайте через importlib.resources:

from importlib.resources import files

data = files("pluralize_ru").joinpath("words.json").read_text(encoding="utf-8")

Hatchling включает такие файлы в wheel автоматически. setuptools без дополнительных настроек их пропускает — проверено на setuptools 84: в wheel попадают только .py. Нужно явно перечислить шаблоны:

[tool.setuptools.package-data]
pluralize_ru = ["*.json"]

Публикация в TestPyPI

TestPyPI — отдельная копия каталога для экспериментов. Пакеты оттуда периодически удаляются, но для первой загрузки это идеальная площадка: ошибки в имени, README или токене обнаружатся без последствий.

  1. Зарегистрируйтесь на test.pypi.org, подтвердите почту и включите двухфакторную аутентификацию.
  2. В настройках аккаунта (раздел API tokens) создайте токен. Для первой загрузки область действия — «Entire account», потому что проекта ещё нет. Скопируйте токен сразу: второй раз его не покажут.
  3. Загрузите архивы:
python -m twine upload --repository testpypi dist/*

twine запросит токен — вставьте его целиком, вместе с префиксом pypi-. Ввод скрыт, символы на экране не появятся. Имя пользователя не нужно: для PyPI и TestPyPI twine подставляет служебное __token__ сам.

Чтобы не вводить токен каждый раз, сохраните его в файле .pypirc в домашней папке (%USERPROFILE%\.pypirc на Windows, ~/.pypirc на macOS и Linux):

[testpypi]
username = __token__
password = pypi-ВАШ_ТОКЕН

Или передайте его через переменную окружения TWINE_PASSWORD — так удобнее в CI. Не храните токен в репозитории.

Проверьте установку из TestPyPI в новом окружении:

python -m pip install --index-url https://test.pypi.org/simple/ --no-deps pluralize-ru

Флаг --no-deps отключает установку зависимостей: в TestPyPI нет большинства настоящих библиотек, и pip может не найти зависимость или поставить вместо неё что-то неожиданное. Если у пакета есть зависимости, поставьте их отдельно из обычного PyPI.

Публикация в PyPI

Когда всё работает, повторите то же на основном сервере:

  1. Зарегистрируйтесь на pypi.org (аккаунт TestPyPI здесь не действует), включите 2FA и создайте токен.
  2. Выполните загрузку — без --repository twine отправляет файлы на pypi.org:
python -m twine upload dist/*
  1. Проверьте установку, как это сделает пользователь:
python -m pip install pluralize-ru

После первой загрузки проект появится в аккаунте. Удалите токен с доступом ко всему аккаунту и выпустите новый, ограниченный этим проектом: утечка такого токена не затронет другие ваши пакеты. Для разных компьютеров и CI удобно держать отдельные токены — их можно отзывать по одному.

Сборка и публикация через uv

uv заменяет связку venv, pip, build и twine одной программой. Если вы им пользуетесь, путь короче:

uv init --lib pluralize-ru     # src-структура, pyproject.toml с uv_build
cd pluralize-ru
uv build                       # → dist/*.whl и dist/*.tar.gz
uv publish --index testpypi    # загрузка в TestPyPI (индекс описан ниже)
uv publish                     # загрузка в PyPI

uv init --lib создаёт src/pluralize_ru/__init__.py, файл-маркер py.typed для проверки типов, README.md, .python-version и pyproject.toml с бэкендом uv_build. Токен передают флагом --token или переменной UV_PUBLISH_TOKEN. Чтобы uv publish --index testpypi знал адрес TestPyPI, добавьте в pyproject.toml:

[[tool.uv.index]]
name = "testpypi"
url = "https://test.pypi.org/simple/"
publish-url = "https://test.pypi.org/legacy/"
explicit = true

Перед публикацией собирайте командой uv build --no-sources: так uv игнорирует локальные источники из tool.uv.sources и проверяет, что пакет соберётся у всех.

Trusted Publishing: публикация из CI без токенов

Trusted Publishing (доверенная публикация) позволяет загружать пакеты из CI без постоянного API-токена. Вы один раз указываете на PyPI, из какого репозитория и workflow разрешена публикация. При каждом запуске CI получает от PyPI короткоживущий токен через OpenID Connect, и украсть из секретов репозитория нечего. Поддерживаются GitHub Actions, GitLab CI/CD, Google Cloud и ActiveState.

Настройка для GitHub Actions:

  1. На pypi.org откройте Account settings → Publishing и добавьте «pending publisher»: имя проекта, владельца и имя репозитория, имя файла workflow (publish.yml) и окружение pypi. Проект будет создан при первой публикации — заранее загружать его вручную не нужно.
  2. В настройках репозитория на GitHub создайте окружение pypi и включите для него ручное подтверждение запуска.
  3. Добавьте workflow .github/workflows/publish.yml:
name: Publish to PyPI
on:
  push:
    tags: ['v*']

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v6
        with:
          persist-credentials: false
      - uses: actions/setup-python@v6
        with:
          python-version: '3.x'
      - run: python3 -m pip install build --user
      - run: python3 -m build
      - uses: actions/upload-artifact@v5
        with:
          name: python-package-distributions
          path: dist/

  publish-to-pypi:
    needs: build
    runs-on: ubuntu-latest
    environment:
      name: pypi
      url: https://pypi.org/p/pluralize-ru
    permissions:
      id-token: write # обязательно для Trusted Publishing
    steps:
      - uses: actions/download-artifact@v6
        with:
          name: python-package-distributions
          path: dist/
      - uses: pypa/gh-action-pypi-publish@release/v1

Теперь выпуск — это тег: git tag v0.1.0 && git push origin v0.1.0. Сборка и загрузка разнесены по разным заданиям намеренно: токен PyPI может получить только задание публикации, а код сборки выполняется без этого права. Действие pypa/gh-action-pypi-publish дополнительно прикладывает к файлам аттестации (PEP 740), подтверждающие, где они собраны. uv publish в GitHub Actions тоже умеет Trusted Publishing без дополнительных параметров.

Версии и выпуск обновлений

Формат версий в Python задаёт PEP 440. Для библиотек обычно используют схему MAJOR.MINOR.PATCH:

ИзменениеБылоСтало
исправили ошибку, интерфейс не менялся0.1.00.1.1
добавили функцию, старый код работает0.1.10.2.0
изменили или удалили публичный интерфейс0.2.01.0.0

Предварительные выпуски помечают суффиксами: 1.0.0a1 (альфа), 1.0.0b1 (бета), 1.0.0rc1 (кандидат в релиз). pip не ставит их по умолчанию — только с флагом --pre или при явном указании версии, поэтому они безопасны для тестирования на живых пользователях.

Порядок выпуска обновления:

  1. Поднимите версию в pyproject.toml.
  2. Удалите dist/, пересоберите и проверьте пакет.
  3. Загрузите новые файлы.

Один и тот же номер версии загрузить повторно нельзя — даже если удалить выпуск или весь проект. Если в выпуск попала ошибка, выпустите следующую версию, а неудачную пометьте как yanked («отозванную») в настройках выпуска на PyPI. pip перестанет выбирать её автоматически, но установка по точному номеру ==0.1.1 продолжит работать, и чужие проекты, закрепившие эту версию, не сломаются.

Чтобы версия хранилась в одном месте, в учебном пакете __version__ читается из метаданных через importlib.metadata. Обратный вариант — хранить версию в коде, а бэкенд пусть читает её оттуда. В Hatchling это выглядит так:

[project]
name = "pluralize-ru"
dynamic = ["version"]

[tool.hatch.version]
path = "src/pluralize_ru/__about__.py"   # файл со строкой __version__ = "0.2.0"

В uv поднимать версию можно командой: uv version --bump minor превратит 0.1.0 в 0.2.0 прямо в pyproject.toml.

Частые ошибки при публикации

Сообщение или симптомПричинаРешение
400 File already existsтакая версия уже загружаласьподнять версию и пересобрать
403 Forbidden при загрузкетокен от другого сервера (TestPyPI вместо PyPI) или неполныйвставить токен целиком с префиксом pypi-, проверить --repository
403: нет прав на проектимя уже занято чужим проектом или слишком похоже на неговыбрать другое имя в name
ModuleNotFoundError после установкипапка в src/ называется не так, как вы импортируетесверить имя папки и import; открыть wheel через zipfile -l
в wheel нет JSON или шаблоновsetuptools не включил данные[tool.setuptools.package-data] или переход на Hatchling
описание на PyPI отображается текстом без разметкиневерный формат READMEпроверить расширение файла и twine check

Если причина ошибки импорта неочевидна, пройдитесь по разбору ModuleNotFoundError. Для подробного вывода при загрузке добавьте --verbose: python -m twine upload --verbose dist/*.

Частые вопросы

Можно ли пользоваться библиотекой, не публикуя её в PyPI?

Да. pip устанавливает пакет из локальной папки (pip install ./pluralize-ru), из собранного файла (pip install pluralize_ru-0.1.0-py3-none-any.whl) и прямо из Git-репозитория: pip install "git+https://github.com/ivan/pluralize-ru.git@v0.1.0". Для команды этого часто достаточно: доступ к репозиторию ограничивает круг пользователей.

Как сделать приватный пакет?

Всё, что загружено на PyPI, видно всем. Для закрытого кода используйте установку из приватного Git-репозитория или собственный индекс пакетов (GitLab Package Registry, pypiserver, devpi), а публикуйте туда той же командой twine с параметром --repository-url. Чтобы случайно не отправить внутренний пакет на PyPI, добавьте классификатор "Private :: Do Not Upload" — PyPI отклоняет такие файлы.

Нужен ли ещё setup.py?

Для нового проекта на чистом Python — нет: всё описывается в pyproject.toml. setup.py оставляют, когда нужна программная логика сборки, например компиляция расширений на C в setuptools. Сам файл не запрещён, устарели прямые вызовы python setup.py ... из командной строки.

Как удалить пакет с PyPI?

Владелец проекта может удалить выпуск или весь проект в настройках на pypi.org. Удаление необратимо, а номера удалённых версий занять повторно нельзя. Если пакетом уже пользуются, вместо удаления пометьте выпуск как yanked и выпустите исправленную версию.

Подойдёт ли Poetry вместо этой связки?

Да. Poetry создаёт структуру проекта, управляет зависимостями и публикует командами poetry build и poetry publish. С версии 2.0 Poetry понимает стандартную таблицу [project], поэтому переход между инструментами стал проще. Выбор между pip+build+twine, uv и Poetry — вопрос рабочего процесса: формат пакета на PyPI у всех одинаковый.

Как подключить свой пакет к другому проекту, пока он в разработке?

Активируйте окружение другого проекта и выполните pip install -e /путь/к/pluralize-ru. Правки в библиотеке сразу станут видны в проекте, а когда выпустите версию на PyPI, замените строку на обычную зависимость. Как устанавливать и обновлять пакеты, подробно описано в руководстве по pip.

Итог

  • Код — в src/имя_пакета/, метаданные и бэкенд сборки — в pyproject.toml; для чистого Python подойдёт Hatchling.
  • Во время разработки ставьте пакет командой pip install -e . и гоняйте тесты против установленной версии.
  • python -m build собирает wheel и sdist; перед загрузкой проверьте их через twine check, просмотр содержимого и установку в чистое окружение.
  • Первую загрузку делайте на TestPyPI, затем twine upload dist/* на PyPI с токеном, ограниченным проектом, — или настройте Trusted Publishing и выпускайте версии тегами из CI.
  • Каждый выпуск получает новый номер версии: заменить загруженный файл нельзя, неудачную версию отзывают через yank.
  • #Python
  • #PyPI
  • #pyproject.toml
  • #Пакеты
  • #twine
↑ ↓ выборEnter открытьEsc закрыть