Claude Code for PyPI Package Publishing (2026)
Publishing Python packages to PyPI (Python Package Index) is a fundamental skill for any Python developer. Whether you’re sharing a utility library, a framework, or a tool with the community, having a streamlined workflow saves time and reduces errors. This guide shows you how to use Claude Code to automate and simplify your PyPI publishing workflow, from initial project scaffolding through to automated CI/CD releases.
Setting Up Your Project Structure
Before publishing to PyPI, your project needs a proper structure. Claude Code can help you set this up correctly from the start. A standard Python package structure includes:
my_package/
my_package/
__init__.py
core.py
tests/
__init__.py
test_core.py
pyproject.toml
README.md
LICENSE
CHANGELOG.md
The essential file is pyproject.toml, which defines your package metadata. Here’s a minimal example:
[build-system]
requires = ["setuptools>=61.0", "wheel"]
build-backend = "setuptools.build_meta"
[project]
name = "my-package"
version = "0.1.0"
description = "A short description of what this package does"
readme = "README.md"
license = {text = "MIT"}
authors = [
{name = "Your Name", email = "[email protected]"}
]
requires-python = ">=3.8"
dependencies = [
"requests>=2.28.0",
]
[project.urls]
Homepage = "https://github.com/yourusername/my-package"
Choosing a Build Backend
The pyproject.toml [build-system] section tells pip which tool to use to build your package. Three backends dominate the ecosystem today:
| Backend | Best for | Pros | Cons |
|---|---|---|---|
| setuptools | Most packages, legacy projects | Mature, widely supported, familiar | More verbose config |
| Hatch | New projects, modern workflows | Clean config, built-in env management | Smaller ecosystem familiarity |
| Flit | Pure Python packages | Minimal config, fast | No C extensions support |
| Poetry | Projects needing dependency management too | All-in-one tool | Non-standard lockfile, slower |
For a typical utility library with no C extensions, either setuptools or Hatch is a solid choice. Here is a pyproject.toml using Hatch:
[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"
[project]
name = "my-package"
version = "0.1.0"
description = "A short description of what this package does"
readme = "README.md"
license = "MIT"
authors = [
{name = "Your Name", email = "[email protected]"},
]
requires-python = ">=3.8"
dependencies = [
"requests>=2.28.0",
]
[tool.hatch.version]
path = "my_package/__init__.py"
With tool.hatch.version, Hatch reads the version directly from your __init__.py, so you maintain one canonical source of truth.
Exposing Console Scripts
If your package includes a command-line tool, declare it in pyproject.toml so pip installs it automatically:
[project.scripts]
my-tool = "my_package.cli:main"
After pip install my-package, users can run my-tool directly from their shell. Claude Code can help you scaffold the cli.py module and wire up argument parsing with argparse or click.
Creating a Claude Skill for PyPI Publishing
You can create a custom Claude Skill that encapsulates your PyPI publishing workflow. This skill will guide you through each step and ensure consistency. Here’s how to structure it:
---
name: "Publish to PyPI"
description: "Guide through the complete PyPI package publishing workflow"
---
PyPI Publishing Workflow
This skill will help you publish your Python package to PyPI.
Step 1: Verify Package Structure
Before building, ensure your package has:
- A proper pyproject.toml
- A README.md with clear documentation
- A LICENSE file
- All dependencies correctly specified
Step 2: Build the Package
Run the build command:
python -m build
Step 3: Upload to Test PyPI
First, test your upload on Test PyPI:
python -m twine upload --repository testpypi dist/*
Step 4: Verify the Test Install
Test that the package installs correctly:
pip install --index-url https://test.pypi.org/simple/ your-package
Step 5: Upload to Production PyPI
Once verified, upload to the real PyPI:
python -m twine upload dist/*
This skill gives Claude Code a reusable playbook it can follow every time you ask it to publish a release. Instead of remembering every step yourself, you invoke the skill and let the automation handle it.
Automating the Build and Upload Process
Claude Code can execute the entire publishing workflow for you. Here’s a practical example of how to automate this:
Install required build tools
pip install build twine
Clean previous builds
rm -rf build/ dist/ *.egg-info
Build the package
python -m build
Upload to PyPI (using Twine)
python -m twine upload dist/*
You can create a shell script that Claude Code executes, making the process repeatable:
#!/bin/bash
publish.sh - PyPI Publishing Script
set -e
echo "Building package..."
rm -rf build dist *.egg-info
python -m build
echo "Uploading to PyPI..."
python -m twine upload dist/*
echo "Done! Package published successfully."
For a more solid script that validates the package before uploading, add a Twine check step:
#!/bin/bash
publish.sh - PyPI Publishing Script with validation
set -euo pipefail
VERSION=$(python -c "import my_package; print(my_package.__version__)")
echo "Publishing version $VERSION..."
Clean
rm -rf build dist *.egg-info
Build source distribution and wheel
python -m build
Validate the distributions before uploading
echo "Validating distributions..."
python -m twine check dist/*
Check for common issues
echo "Running package checks..."
python -m pip install --quiet dist/*.whl --dry-run
Upload
echo "Uploading to PyPI..."
python -m twine upload dist/*
echo "Published $VERSION successfully."
echo "View at: https://pypi.org/project/my-package/$VERSION/"
twine check catches malformed metadata, missing fields, and README rendering issues before they become visible to users on the PyPI page. It is one of the most under-used steps in the Python publishing workflow.
Managing Version Numbers
Version management is crucial for package publishing. Semantic versioning (SemVer) is the standard approach:
- Major (1.0.0 → 2.0.0): Breaking changes
- Minor (1.0.0 → 1.1.0): New features, backward compatible
- Patch (1.0.0 → 1.0.1): Bug fixes
You can use Python’s bump2version or bumpver tools to automate version increments:
Install bump2version
pip install bump2version
Bump patch version
bumpversion patch
Bump minor version
bumpversion minor
Bump major version
bumpversion major
Claude Code can help you update version numbers across all files in your project, including pyproject.toml, __init__.py, and documentation.
Using bumpver for Multi-File Version Management
bumpver is a more modern alternative that handles complex version strings and multi-file updates cleanly. Configure it in pyproject.toml:
[tool.bumpver]
current_version = "0.1.0"
version_pattern = "MAJOR.MINOR.PATCH"
commit_message = "Bump version {old_version} -> {new_version}"
tag_message = "v{new_version}"
tag_scope = "default"
commit = true
tag = true
push = false
[[tool.bumpver.file_patterns]]
fileset = ["pyproject.toml"]
patterns = ['version = "{version}"']
[[tool.bumpver.file_patterns]]
fileset = ["my_package/__init__.py"]
patterns = ['__version__ = "{version}"']
[[tool.bumpver.file_patterns]]
fileset = ["CHANGELOG.md"]
patterns = ["## Unreleased", "## {version} ({date})"]
Now a single command updates all version references, commits the change, and creates a git tag:
bumpver update --patch
or
bumpver update --minor
or
bumpver update --major
Claude Code can run this for you and then immediately trigger the publishing workflow, keeping the version bump and the PyPI upload as one atomic step.
Dynamic Versioning from Git Tags
An alternative approach eliminates version numbers in source files entirely by deriving the version from git tags at build time:
[build-system]
requires = ["setuptools>=61.0", "setuptools-scm"]
build-backend = "setuptools.build_meta"
[tool.setuptools_scm]
write_to = "my_package/_version.py"
my_package/__init__.py
from ._version import version as __version__
With this setup, python -m build reads the current git tag (e.g., v1.2.3) and writes it into the distribution. You never manually edit a version number again. You just tag the commit and build.
Using API Tokens for Secure Authentication
Never use your PyPI password directly. Instead, use API tokens for secure authentication:
- Generate a token at pypi.org/manage/account
- Create a
.pypircfile in your home directory:
[pypi]
username = __token__
password = pypi-AgEIcHlwaS5vcmc...
[testpypi]
username = __token__
password = pypi-AgEIcHlwaS5vcmc...
Claude Code can help you set this up securely using environment variables or a secrets manager.
Scoped Tokens Are Safer Than Account Tokens
PyPI supports project-scoped tokens that only have permission to upload to a specific package. Use these in preference to account-wide tokens:
- Go to pypi.org → Your Projects → Manage → Settings → API tokens
- Select “Scope: Project: my-package”
- Copy the token and store it as a CI secret named
PYPI_TOKEN
If a project-scoped token is ever compromised, the attacker can only upload new versions of that one package. An account-wide token would let them tamper with every package in your account.
Trusted Publishers: Eliminating Token Secrets Entirely
PyPI now supports “Trusted Publishers” via OpenID Connect (OIDC), which means GitHub Actions workflows can publish without storing any secret token at all:
- On PyPI, go to your project → Settings → Publishing → Add a new pending publisher
- Fill in your GitHub repository and workflow filename
- In your workflow, use the
pypa/gh-action-pypi-publishaction withid-token: writepermission
name: Publish to PyPI
on:
release:
types: [published]
permissions:
id-token: write # Required for trusted publisher OIDC
jobs:
publish:
runs-on: ubuntu-latest
environment: pypi # Optional: use a protected GitHub Environment
steps:
- uses: actions/checkout@v4
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: '3.x'
- name: Install build tools
run: pip install build
- name: Build package
run: python -m build
- name: Publish to PyPI
uses: pypa/gh-action-pypi-publish@release/v1
# No secrets needed. OIDC handles authentication
This is now the recommended approach for new packages. It removes the operational burden of rotating API tokens and eliminates an entire category of secret-leakage risk.
Publishing to Test PyPI First
Always test your package on Test PyPI before releasing to production:
Upload to Test PyPI
python -m twine upload --repository testpypi dist/*
Install and test locally
pip install --index-url https://test.pypi.org/simple/ your-package
Verify imports work
python -c "import your_package; print(your_package.__version__)"
This workflow prevents embarrassing mistakes from reaching production.
What to Check on Test PyPI
After installing from Test PyPI, run a quick smoke test checklist before promoting to production:
1. Version matches what you expect
python -c "import my_package; print(my_package.__version__)"
2. Entry points work (if your package has CLI tools)
my-tool --version
3. Core imports succeed
python -c "from my_package import MyMainClass; print('OK')"
4. Dependencies are all present
python -c "import my_package; my_package.run_diagnostics()"
If any of these fail on Test PyPI, you catch the issue before it affects the thousands of developers who might install from production PyPI.
Continuous Integration for Automated Publishing
You can set up GitHub Actions to automate PyPI publishing on tags:
name: Publish to PyPI
on:
release:
types: [published]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: '3.x'
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install build twine
- name: Build package
run: python -m build
- name: Publish to PyPI
env:
TWINE_USERNAME: __token__
TWINE_PASSWORD: ${{ secrets.PYPI_TOKEN }}
run: twine upload dist/*
Adding a Test Gate Before Publishing
A more solid pipeline runs your test suite before allowing the release to proceed:
name: Test and Publish
on:
push:
branches: [main]
release:
types: [published]
jobs:
test:
runs-on: ubuntu-latest
strategy:
matrix:
python-version: ["3.8", "3.9", "3.10", "3.11", "3.12"]
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}
- name: Install package with test dependencies
run: pip install -e ".[test]"
- name: Run tests
run: pytest tests/ -v --tb=short
publish:
needs: test # Only runs if test job succeeds
if: github.event_name == 'release'
runs-on: ubuntu-latest
permissions:
id-token: write
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: '3.x'
- name: Build package
run: |
pip install build
python -m build
- name: Publish to PyPI
uses: pypa/gh-action-pypi-publish@release/v1
The needs: test line is the critical gate. The publish job cannot start unless tests pass on all Python versions in the matrix.
Writing a Good README for PyPI
The README is the first thing potential users see on your PyPI package page. PyPI renders README.md as the long description, so invest time in making it clear. Essential sections:
my-package
One-sentence description of what the package does.
Installation
pip install my-package
Quick Start
from my_package import MyMainClass
obj = MyMainClass(config="value")
result = obj.process("input")
print(result)
Documentation
Full docs at https://my-package.readthedocs.io
Contributing
Issues and PRs welcome. See CONTRIBUTING.md.
License
MIT
Claude Code can draft this README for you based on your package’s source code, inferring the purpose from module docstrings and function signatures.
Handling Package Classifiers
Classifiers help users find your package in PyPI search. Add them to pyproject.toml:
[project]
classifiers = [
"Development Status :: 4 - Beta",
"Intended Audience :: Developers",
"License :: OSI Approved :: MIT License",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.8",
"Programming Language :: Python :: 3.9",
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
"Topic :: Software Development :: Libraries :: Python Modules",
"Typing :: Typed",
]
The Development Status classifier is especially important: it tells users whether the API is stable. Use 3 - Alpha for early releases, 4 - Beta for mostly-stable packages still gaining features, and 5 - Production/Stable for packages you are ready to support long-term.
Best Practices for Package Publishing
Follow these guidelines for successful package publishing:
- Choose a unique package name: Check PyPI to ensure your name isn’t taken
- Write a clear description: This appears in PyPI search results
- Include dependencies correctly: Only list actual dependencies
- Add classifiers: Help users find your package with proper classifiers
- Write a good README: Include installation instructions and usage examples
- Use version control tags: Tag releases for traceability
Include Type Hints and a py.typed Marker
Modern Python packages should include type hints so users get IDE autocompletion and mypy integration. Add an empty py.typed file to your package to signal that types are available:
touch my_package/py.typed
Then declare it as package data in pyproject.toml:
[tool.setuptools.package-data]
my_package = ["py.typed"]
This is a one-line change that dramatically improves the developer experience for anyone using your library.
Maintain a Changelog
A CHANGELOG.md following the Keep a Changelog format makes it easy for users to understand what changed between versions:
Changelog
[Unreleased]
[1.2.0] - 2026-03-15
Added
- Support for async context managers
- New `batch_process` method
Fixed
- Memory leak when processing large inputs (#42)
[1.1.0] - 2026-01-10
Added
- Initial public API
Claude Code can help you maintain this file by summarizing git commits since the last release and formatting them into the correct CHANGELOG sections.
Common Pitfalls to Avoid
Publishing without testing the install. Always install and smoke-test from Test PyPI before the production upload. Issues like missing package data or wrong entry points only appear at install time.
Forgetting to include non-Python files. If your package includes data files (JSON configs, templates, static assets), you must explicitly include them:
[tool.setuptools.package-data]
my_package = ["data/*.json", "templates/*.html"]
Using relative imports inconsistently. Test your package both by running it directly and by installing it via pip. Relative imports that work when running python my_package/core.py may fail when the package is installed.
Not pinning the minimum Python version. If your code uses f-strings, walrus operators, or match statements, set requires-python to the earliest version that supports those features. Users on older Python will get a clear error rather than a confusing import failure.
Uploading the same version twice. PyPI does not allow re-uploading a version. If you discover a bug in a just-published release, you must bump to a new patch version. Always run twine check dist/* before uploading and test locally first.
Leaving debug code in the package. Run grep -r "breakpoint\|pdb\|ipdb" my_package/ before building to catch any leftover debug statements. Claude Code can add this as a pre-build check in your publishing script.
Conclusion
Claude Code makes PyPI package publishing straightforward by guiding you through each step, executing build commands, and helping you maintain proper project structure. By creating a reusable skill for publishing, you can standardize your workflow and reduce the chance of errors.
Remember to always test on Test PyPI before production, use API tokens or Trusted Publishers for authentication, maintain a proper CHANGELOG, and consider setting up CI/CD for automated releases. With these practices in place, sharing your Python packages with the world becomes a reliable and repeatable process. The investment in a solid publishing workflow pays dividends every release cycle. and with Claude Code orchestrating the steps, each release takes minutes rather than the careful manual work that tripped up developers before good tooling existed.
Try it: Paste your error into our Error Diagnostic for an instant fix.
Related Reading
- Getting Started Guide. From zero to productive with Claude Code
- Claude Code for NPM Package Publishing Workflow Guide
- Claude Code for Artifact Publishing Workflow Tutorial
- Claude Code for Cargo Crate Publishing Workflow Guide
- Claude Code for Package Registry Workflow Tutorial
- Claude Code for Maven Artifact Publishing Workflow
- Claude Code for Backstage Software Catalog Workflow
- Claude Code for ArgoCD App of Apps Workflow
- Claude Code for Russian Developer Backend Workflow
- Claude Code Rspec Ruby Bdd — Complete Developer Guide
- Claude Code for Chart Museum Workflow Tutorial
- Claude Code for Conventional Commits Workflow Guide
Built by theluckystrike. More at zovo.one
Get started → Generate your project setup with our Project Starter.