Как создать свой пакет на Python и опубликовать в PyPI
Создание библиотеки Python: src-структура, pyproject.toml, выбор бэкенда, pip install -e, python -m build, TestPyPI, twine, uv и Trusted Publishing.
Содержание · 14 разделов
- Что понадобится
- Структура проекта: src-layout
- Имя проекта и имя пакета
- Код библиотеки
- README и лицензия
- Файл pyproject.toml
- Выбор бэкенда сборки
- Метаданные проекта
- Зависимости
- Консольная команда
- Установка в режиме разработки: pip install -e
- Сборка: python -m build
- Проверка перед публикацией
- Если в пакете есть данные
- Публикация в TestPyPI
- Публикация в PyPI
- Сборка и публикация через uv
- Trusted Publishing: публикация из CI без токенов
- Версии и выпуск обновлений
- Частые ошибки при публикации
- Частые вопросы
- Можно ли пользоваться библиотекой, не публикуя её в PyPI?
- Как сделать приватный пакет?
- Нужен ли ещё setup.py?
- Как удалить пакет с PyPI?
- Подойдёт ли Poetry вместо этой связки?
- Как подключить свой пакет к другому проекту, пока он в разработке?
- Итог
Чтобы создать свой пакет на 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], поэтому выбор влияет в основном на дополнительные настройки и на то, какие файлы попадут в архив.
| Бэкенд | Строка requires | build-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 или токене обнаружатся без последствий.
- Зарегистрируйтесь на test.pypi.org, подтвердите почту и включите двухфакторную аутентификацию.
- В настройках аккаунта (раздел API tokens) создайте токен. Для первой загрузки область действия — «Entire account», потому что проекта ещё нет. Скопируйте токен сразу: второй раз его не покажут.
- Загрузите архивы:
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
Когда всё работает, повторите то же на основном сервере:
- Зарегистрируйтесь на pypi.org (аккаунт TestPyPI здесь не действует), включите 2FA и создайте токен.
- Выполните загрузку — без
--repositorytwine отправляет файлы на pypi.org:
python -m twine upload dist/*
- Проверьте установку, как это сделает пользователь:
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:
- На pypi.org откройте Account settings → Publishing и добавьте «pending publisher»: имя проекта, владельца и имя репозитория, имя файла workflow (
publish.yml) и окружениеpypi. Проект будет создан при первой публикации — заранее загружать его вручную не нужно. - В настройках репозитория на GitHub создайте окружение
pypiи включите для него ручное подтверждение запуска. - Добавьте 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.0 | 0.1.1 |
| добавили функцию, старый код работает | 0.1.1 | 0.2.0 |
| изменили или удалили публичный интерфейс | 0.2.0 | 1.0.0 |
Предварительные выпуски помечают суффиксами: 1.0.0a1 (альфа), 1.0.0b1 (бета), 1.0.0rc1 (кандидат в релиз). pip не ставит их по умолчанию — только с флагом --pre или при явном указании версии, поэтому они безопасны для тестирования на живых пользователях.
Порядок выпуска обновления:
- Поднимите версию в
pyproject.toml. - Удалите
dist/, пересоберите и проверьте пакет. - Загрузите новые файлы.
Один и тот же номер версии загрузить повторно нельзя — даже если удалить выпуск или весь проект. Если в выпуск попала ошибка, выпустите следующую версию, а неудачную пометьте как 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