pypiguide.site

Release your first Python package safely.

PyPI Guide is a practical, step-by-step path from idea validation to TestPyPI, PyPI, clean install testing, token security, and future version updates.

14 first-release steps
2 test install paths
1 repeatable update flow
$ python -m build
Successfully built mypackage-0.0.1.tar.gz
Successfully built mypackage-0.0.1-py3-none-any.whl

$ python -m twine check dist/*
Checking dist/mypackage-0.0.1-py3-none-any.whl: PASSED
Checking dist/mypackage-0.0.1.tar.gz: PASSED

$ python -m twine upload --repository testpypi dist/*
Uploading distributions to https://test.pypi.org/legacy/
View at:
https://test.pypi.org/project/mypackage/0.0.1/
Build Clean artifacts
Test Fresh install
Release Secure tokens

What this guide helps you do

Publishing a package is not only a command. You need a clear idea, a safe name, clean metadata, local tests, TestPyPI validation, a real PyPI release, and secure token handling afterward.

Plan before packaging

Start with feedback from an adviser, teammate, or AI assistant so you know what problem the package solves.

Test without guessing

Build locally, check metadata, upload to TestPyPI, and install in a clean temporary project before real release.

Release safely

Use account-wide tokens only for the first upload, then replace them with project-scoped tokens.

The release path at a glance

Keep this mental model: local proof first, test registry second, real registry last. Most beginner mistakes happen when these steps are skipped or mixed together.

Safe order
1

Build locally

Run tests, build distributions, and check metadata.

2

Upload to TestPyPI

Practice the release with a separate test registry.

3

Install cleanly

Use a temporary folder so local source files cannot fool you.

4

Publish to PyPI

Upload the same checked artifacts to the real index.

5

Secure tokens

Replace broad upload tokens with project-scoped tokens.

The full beginner workflow

Follow these steps in order. They are written for a first package, but the same release discipline works for every future update.

01

Discuss the idea before building

Start by explaining the package idea to an adviser, experienced developer, teammate, or AI assistant. Ask what problem it solves, who would use it, what the smallest useful version should do, and whether an existing package already solves most of it.

Good first question: “What is the smallest package I can publish that proves this idea is useful?”
02

Choose a clear package name

Pick a name that is short, relevant, easy to pronounce, easy to type, and connected to the package purpose. Avoid names that are too broad, too clever, or close to an existing popular project.

  • Prefer descriptive names over jokes.
  • Check spelling and possible confusion.
  • Use the same name for PyPI, GitHub, CLI, and docs when possible.
  • Avoid names that could create trademark or brand confusion.
03

Create a clean Python package structure

Use a modern src/ layout. It prevents accidental imports from your working directory and makes install testing more reliable.

Recommended package layout
my-package/
├── pyproject.toml
├── README.md
├── LICENSE
├── CHANGELOG.md
├── src/
│   └── mypackage/
│       ├── __init__.py
│       └── cli.py
├── tests/
│   └── test_cli.py
└── docs/
    └── getting-started.md
pyproject.toml
[build-system]
requires = ["hatchling>=1.18,<1.27"]
build-backend = "hatchling.build"

[project]
name = "mypackage"
version = "0.0.1"
description = "A short, clear description of what your package does."
authors = [{ name = "Your Name" }]
readme = "README.md"
license = { text = "MIT" }
requires-python = ">=3.10"
dependencies = []

[project.urls]
Homepage = "https://github.com/yourname/mypackage"
Repository = "https://github.com/yourname/mypackage"
Issues = "https://github.com/yourname/mypackage/issues"

[project.scripts]
mypackage = "mypackage.cli:main"

[tool.hatch.build.targets.wheel]
packages = ["src/mypackage"]
src/mypackage/cli.py
def main() -> None:
    print("mypackage is installed and working")


if __name__ == "__main__":
    main()
04

Check name availability and brand risk

Check the name before writing too much code. PyPI and TestPyPI are separate registries, so check both. Also search GitHub and the web to avoid confusion with existing projects or brands.

PyPI
Open https://pypi.org/project/mypackage/. If a project page exists, the name is already taken.
TestPyPI
Open https://test.pypi.org/project/mypackage/. TestPyPI has a separate name pool.
GitHub
Search the name and create the repository early if it is available.
Brand risk
Search the web for the exact name and close spellings. Avoid names that look official or misleading.
Check URLs
https://pypi.org/project/mypackage/
https://test.pypi.org/project/mypackage/
https://github.com/yourname/mypackage
Name warning: Do not choose a name that looks like an official package from another company, framework, or product. Even if the upload works, it can create trust and legal problems later.
05

Create accounts and secure tokens

You need accounts on GitHub, TestPyPI, and PyPI. TestPyPI is for practice releases. PyPI is the real public package index.

  • Create or confirm your GitHub account.
  • Create a TestPyPI account at test.pypi.org.
  • Create a PyPI account at pypi.org.
  • Enable two-factor authentication on PyPI and TestPyPI.
  • Create an API token for TestPyPI.
  • Create an API token for PyPI.
Token rule: For the first upload of a brand-new package, use an account-wide token because the project does not exist yet. After the first upload succeeds, delete the broad token and create a project-scoped token.
First upload
Use an account-wide token because PyPI cannot scope a token to a project that does not exist yet.
After upload
Create a new token scoped only to the published project.
Old token
Delete the broad token only after every project or automation that used it has a replacement.
Never paste tokens into chat or source files. Use them only in your terminal prompt, password manager, or trusted CI secret storage.
06

Write code, README, license, and tests

A package does not need to be large, but it should be clear. The README should explain what the package does, why it exists, how to install it, and how to verify it works.

Code
Keep the first version small. Export one useful function or CLI command that proves the package works after install.
README
Write for a stranger: what it does, when to use it, install command, quick example, and expected output.
License
Add a real LICENSE file and match it in pyproject.toml. MIT is common for beginner open-source packages.
Tests
Add at least one test that imports the package and checks the main behavior. This catches packaging mistakes early.
Changelog
Start CHANGELOG.md now, even with one entry, so future version updates have a clear place to live.
README starter
# mypackage

One clear sentence explaining what the package does and who it helps.

## Why

Explain the small problem this package solves. Keep it practical and honest.

## Installation

```bash
pip install mypackage
```

## Quick Start

```bash
mypackage
```

Expected output:

```text
mypackage is installed and working
```

## Development

```bash
python -m pip install --upgrade build twine pytest
python -m pytest -q
```

## License

MIT
Minimal test
from mypackage.cli import main


def test_main_runs(capsys):
    main()
    captured = capsys.readouterr()
    assert "working" in captured.out
CHANGELOG.md starter
# Changelog

## 0.0.1

- First public package structure.
- Added CLI command.
- Added README, license, and install test.
Before moving on: make sure the package imports from a clean shell, the README examples match the real command, and the license name is consistent everywhere.
07

Build locally and run checks

Before uploading anywhere, build the package and validate the distribution metadata. This catches many README, metadata, and packaging errors before they become public.

Clean start
Delete old dist/ files before every build so you never upload stale wheels or source archives.
Tests first
Run the test suite before building. If tests fail locally, the package is not ready for TestPyPI.
Build output
A normal pure-Python build creates both a .tar.gz source archive and a .whl wheel inside dist/.
Twine check
twine check validates package metadata and README rendering before the upload step.
Install build tools
python -m pip install --upgrade build twine pytest
Run tests and build
python -m pytest -q
rm -rf dist
python -m build
python -m twine check dist/*
Expected dist files
dist/
├── mypackage-0.0.1.tar.gz
└── mypackage-0.0.1-py3-none-any.whl
Inspect the build
python -m pip install --force-reinstall dist/*.whl
python -c "import mypackage; print(mypackage.__name__)"
Success should look like: Successfully built ... and Checking dist/...: PASSED.
If `twine check` fails: fix the README, project metadata, license field, or broken links first. Do not upload a package that fails the metadata check.
If `python -m twine` is missing: install it in the same virtual environment or Conda environment you are using for the release: python -m pip install --upgrade twine.
Rule of thumb: the only files you upload should be the fresh files currently inside dist/ after tests, build, and twine check all pass.
08

Push the initial GitHub commit

Push your source code to GitHub so users can inspect it and PyPI can link to the repository. If your project is not already a Git repo, initialize it first.

Initial GitHub push
git init
git add .
git commit -m "Initial package release"
git branch -M main
git remote add origin https://github.com/yourname/mypackage.git
git push -u origin main
If the repo already has Git history: do not run git init again. Commit and push using your existing repository setup.
09

Upload to TestPyPI first

TestPyPI lets you verify the publish process without making the package available on the real PyPI index.

Use TestPyPI
Sign in at test.pypi.org and create a TestPyPI token. A real PyPI token will not work here.
Upload fresh files
Upload only the files from the latest successful build in dist/. Do not rebuild between twine check and this upload.
Username
When Twine asks for a username, type exactly __token__. Paste the token as the password.
First upload
For a brand-new project, the first token usually needs permission to create new projects.
TestPyPI upload
python -m twine upload --repository testpypi dist/*
Twine login prompt
username: __token__
password: pypi-your-testpypi-token-here
Success should show a TestPyPI URL. It usually looks like https://test.pypi.org/project/mypackage/. Open it and check the package name, version, README, author, and project links.
Do not upload to real PyPI yet. First install from TestPyPI in a clean folder in the next step. That proves users can install what you uploaded.
If upload fails with a permission error: confirm you used a TestPyPI token, not a real PyPI token, and confirm the token scope allows uploads for new projects.
10

Install from TestPyPI in a clean folder

Do not test inside your package source folder. Python may import local files and hide an installation problem. Use a temporary folder and a fresh environment.

Leave the repo
Move to a temporary folder before installing. If you stay in the source repo, Python can accidentally import local files.
Fresh venv
Create a new virtual environment so the test starts without old packages, editable installs, or cached imports.
Exact version
Install the version you just uploaded, such as your-exact-package-name==0.0.1, when you want a stricter verification.
Replace the placeholder: use the exact package name from your TestPyPI project page instead of your-exact-package-name. Keep the space after https://pypi.org/simple/; the package name starts after that space. Replace your-cli-command only if your package creates a terminal command.
Example: after installing a package named numpy, verify it with python -c "import numpy". If your package creates a CLI command such as np, verify it with np --help.
macOS or Linux
cd /tmp
python -m venv mypackage-test
source mypackage-test/bin/activate
python -m pip install --upgrade pip
python -m pip install --index-url https://test.pypi.org/simple/ --extra-index-url https://pypi.org/simple/ your-exact-package-name
your-cli-command --help
Windows PowerShell
cd $env:TEMP
py -m venv mypackage-test
.\mypackage-test\Scripts\Activate.ps1
python -m pip install --upgrade pip
python -m pip install --index-url https://test.pypi.org/simple/ --extra-index-url https://pypi.org/simple/ your-exact-package-name
your-cli-command --help
Optional stricter install
python -m pip install --index-url https://test.pypi.org/simple/ --extra-index-url https://pypi.org/simple/ your-exact-package-name==0.0.1
Why the extra index URL? TestPyPI may not have your dependencies. --extra-index-url allows pip to install dependencies from the real PyPI index while installing your package from TestPyPI.
Success should prove the installed package is real. Run the console command, import the package, and try one small real workflow. Do this outside the source repository.
If pip cannot find the package: wait a minute, confirm the TestPyPI project page exists, check the package name spelling, and use the exact version shown on TestPyPI.
11

Upload to real PyPI

If TestPyPI looks correct and a clean install works, upload the same distribution files to PyPI.

Final check
Continue only after the TestPyPI page looks correct and the clean TestPyPI install from step 10 works.
Same files
Upload the same checked files currently in dist/. If you rebuild, go back through tests, build, twine check, and TestPyPI.
Real token
Use a token from pypi.org, not test.pypi.org. The two sites use separate accounts and tokens.
Version lock
Once version 0.0.1 is on PyPI, you cannot replace that same version with changed files.
Real PyPI upload
python -m twine upload dist/*
Twine login prompt
username: __token__
password: pypi-your-real-pypi-token-here
PyPI versions are permanent. You cannot replace version 0.0.1 after uploading it. If you find a problem, fix it, bump the version to 0.0.2, rebuild, and upload the new version.
Pause before pressing enter: real PyPI is public. Check the package name, version, and project links one more time before completing this upload.
12

Verify the live PyPI release

Open the live project page and test installation from real PyPI. This is the final confirmation users can install your package.

Project page
Open the live PyPI URL and confirm the package name, version, summary, README, author, license, and links.
Install command
Test the normal user command: python -m pip install your-exact-package-name.
Fresh venv
Use a new virtual environment so the test proves PyPI install works without local source files.
Command name note: your-exact-package-name is the PyPI package name. your-cli-command is the terminal command installed by the package, if it has one. They can be the same, but they do not have to be. For example, numpy is usually verified with python -c "import numpy". If your package creates a CLI command such as np, verify it with np --help.
Live URL
https://pypi.org/project/your-exact-package-name/
Clean real install test
cd /tmp
python -m venv pypi-live-test
source pypi-live-test/bin/activate
python -m pip install --upgrade pip
python -m pip install your-exact-package-name
your-cli-command --help
Check the details: package name, latest version, description, README rendering, author, license, project links, and CLI help output.
If something looks wrong: do not try to replace the same PyPI version. Fix the project, bump the version, rebuild, test again, and publish a new version.
13

Test in a real application

After the clean temporary install works, try the package inside a real project. This catches practical issues that a minimal test may miss.

Real project test
cd path/to/real-project
python -m pip install your-exact-package-name
your-cli-command --help
Use your real names: install with the exact PyPI package name, then test the CLI command your package actually provides. If your package has no CLI, import it in Python and run one small workflow instead.

If you find a problem, do not try to replace the existing PyPI version. Fix the code, update the version number, rebuild, test, and publish a new version.

14

Replace broad tokens with project-scoped tokens

The first upload often needs an account-wide token because the project does not exist yet. After the first upload succeeds, remove that risk.

  1. Go to PyPI account settings.
  2. Create a new API token.
  3. Choose the scope for only this project.
  4. Save it in a password manager or CI secret store.
  5. Update any release automation to use the new token.
  6. Delete the old all-project token.
If another project still uses the broad token: create a project-scoped token for that project first. Deleting a token does not remove old releases, but it can break future uploads that depend on that token.

How to publish the next version

The second release is where many beginners get surprised: PyPI does not allow replacing an existing version. Every public change needs a new version number, fresh build files, and the same test-release discipline as the first upload.

1

Decide what kind of change you made

For early packages, simple version bumps are enough. Use 0.0.1, 0.0.2, and 0.0.3 while testing the idea. Move toward 0.1.0 when the package has a real public workflow.

Tiny fix
0.0.1 to 0.0.2
First usable MVP
0.0.x to 0.1.0
Stable public release
Consider 1.0.0 only when the API and workflow are stable enough for users to depend on.
2

Update code, tests, docs, and changelog

A release is not only code. Update every file a user depends on to understand the new version.

Code
Finish the bug fix, feature, or cleanup before touching the version number.
Tests
Add or update tests for the changed behavior so the next release is not only manually checked.
README
Re-run the README examples in your head or terminal. Update commands, output, and usage examples that changed.
Docs
Update screenshots, CLI help text, project links, or API notes if users will see different behavior.
CHANGELOG.md example
# Changelog

## 0.0.2

- Add copy buttons to every command snippet.
- Expand TestPyPI and PyPI install verification steps.
- Improve token security guidance.
README rule: if the install command, CLI output, public API, or main workflow changed, update the README before publishing.
Changelog rule: write the changelog before building. It helps you catch missing docs or tests while the change is still fresh.
3

Bump the version in one place

For a simple package, the version usually lives in pyproject.toml. If your package also has __version__ in code, update both or configure one source of truth later.

Never reuse
PyPI rejects files for a version that already exists. If 0.0.1 was uploaded, use 0.0.2 or another new version.
Find all versions
Search your project for the old version string before building, especially in pyproject.toml, __init__.py, README, and changelog.
Build names
After building, the files in dist/ should show the new version number in their filenames.
pyproject.toml version bump
[project]
name = "mypackage"
version = "0.0.2"
Find old version references
grep -R "0.0.1" pyproject.toml README.md CHANGELOG.md src tests
If you change the version after building: delete dist/, rebuild, and run twine check again. The built files do not update themselves.
4

Remove old build files and rebuild

The dist/ folder contains built artifacts. If you forget to clear it, you may accidentally upload an old version.

What is dist?
dist/ is generated output. Removing it does not delete your source code, tests, README, or project settings.
Why remove it?
Old files can stay in dist/. Clearing the folder makes sure you upload only the new version.
Order matters
Run tests, remove dist/, build fresh files, then run twine check.
macOS or Linux
python -m pytest -q
rm -rf dist
python -m build
python -m twine check dist/*
Windows PowerShell
python -m pytest -q
Remove-Item -Recurse -Force dist
python -m build
python -m twine check dist/*
Fresh output should show the new version
dist/
├── mypackage-0.0.2.tar.gz
└── mypackage-0.0.2-py3-none-any.whl
Do not continue until Twine says PASSED. A failed metadata check usually means the README or package metadata needs to be fixed before upload.
If you still see the old version in dist/: stop, fix the version number, remove dist/ again, rebuild, and rerun twine check.
5

Upload the update to TestPyPI

Even after the first release, TestPyPI is still useful. It lets you catch packaging mistakes before the real PyPI release.

New version
Confirm dist/ contains the new version, such as 0.0.2, before uploading.
Same token rule
Use a TestPyPI token here. A real PyPI token belongs only to the real PyPI upload step.
Same files
Upload the checked files from step 4. If you change anything after step 4, rebuild and run twine check again.
Upload update to TestPyPI
python -m twine upload --repository testpypi dist/*
Success should show the new TestPyPI version. Open the TestPyPI project page and confirm the version number, README, and project links before installing it in step 6.
If TestPyPI says the version already exists: bump the version again, rebuild, and upload the new files. You cannot overwrite TestPyPI versions either.
6

Install the new TestPyPI version cleanly

Use a fresh temporary environment and ask for the exact version. This confirms pip can install the release you just uploaded.

Fresh folder
Test outside your source repo so Python cannot import local files by accident.
Exact version
Install the specific new version, such as your-exact-package-name==0.0.2, so you know pip found the update.
Real workflow
Run the CLI command or a small import/use case that proves the changed behavior works after install.
Replace the placeholder: use your exact TestPyPI package name. Keep the space after https://pypi.org/simple/; the package name starts after that space. Replace your-cli-command with the command your package installs, if it has one. Example: verify numpy with python -c "import numpy", or verify a CLI command such as np with np --help.
Clean install exact version
cd /tmp
python -m venv package-update-test
source package-update-test/bin/activate
python -m pip install --upgrade pip
python -m pip install --index-url https://test.pypi.org/simple/ --extra-index-url https://pypi.org/simple/ your-exact-package-name==0.0.2
your-cli-command --help
If pip installs the old version: confirm the exact version exists on TestPyPI, check the package name spelling, and rerun the install with ==0.0.2.
7

Upload the update to real PyPI

When TestPyPI and the clean install test both pass, publish the same artifact files to the real PyPI index.

Final gate
Continue only after the new TestPyPI version installs and the changed behavior works in a clean environment.
Real token
Use the project-scoped PyPI token for this package if you already created one.
Permanent version
Real PyPI will not let you replace 0.0.2 after upload. Problems require another version bump.
Upload update to PyPI
python -m twine upload dist/*
Verify exact real version
python -m pip install --upgrade your-exact-package-name==0.0.2
your-cli-command --help
Verification names: install with the exact PyPI package name, then test the CLI command your package provides. If there is no CLI command, run a small Python import and workflow instead.
If you find a problem after upload: do not try to replace the same version. Fix the issue, bump to the next version, rebuild, test on TestPyPI, and upload again.
8

Tag the release in GitHub

A Git tag makes it easy to find the exact source code for the package version users installed.

Match PyPI
Use a tag that matches the published version, such as v0.0.2 for package version 0.0.2.
Commit first
Commit the code, README, changelog, and version bump before creating the tag.
Push both
Push the branch and the tag so GitHub has the exact source for the PyPI release.
Git tag
git add .
git commit -m "Release 0.0.2"
git tag v0.0.2
git push
git push origin v0.0.2
If there is nothing to commit: skip git add and git commit, then tag the commit that produced the files you uploaded.
Congratulations, your release is complete. Your PyPI version, Git commit, and Git tag should now point to the same source. Share this guide with others who want to publish Python packages more easily, and revisit it whenever you prepare your next release.
Copied to clipboard