Plan before packaging
Start with feedback from an adviser, teammate, or AI assistant so you know what problem the package solves.
pypiguide.site
PyPI Guide is a practical, step-by-step path from idea validation to TestPyPI, PyPI, clean install testing, token security, and future version updates.
$ 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/
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.
Start with feedback from an adviser, teammate, or AI assistant so you know what problem the package solves.
Build locally, check metadata, upload to TestPyPI, and install in a clean temporary project before real release.
Use account-wide tokens only for the first upload, then replace them with project-scoped tokens.
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.
Run tests, build distributions, and check metadata.
Practice the release with a separate test registry.
Use a temporary folder so local source files cannot fool you.
Upload the same checked artifacts to the real index.
Replace broad upload tokens with project-scoped tokens.
Follow these steps in order. They are written for a first package, but the same release discipline works for every future update.
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.
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.
Use a modern src/ layout. It prevents accidental
imports from your working directory and makes install testing
more reliable.
my-package/
├── pyproject.toml
├── README.md
├── LICENSE
├── CHANGELOG.md
├── src/
│ └── mypackage/
│ ├── __init__.py
│ └── cli.py
├── tests/
│ └── test_cli.py
└── docs/
└── getting-started.md
[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"]
def main() -> None:
print("mypackage is installed and working")
if __name__ == "__main__":
main()
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.
https://pypi.org/project/mypackage/. If a
project page exists, the name is already taken.
https://test.pypi.org/project/mypackage/.
TestPyPI has a separate name pool.
https://pypi.org/project/mypackage/
https://test.pypi.org/project/mypackage/
https://github.com/yourname/mypackage
You need accounts on GitHub, TestPyPI, and PyPI. TestPyPI is for practice releases. PyPI is the real public package index.
test.pypi.org.pypi.org.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.
LICENSE file and match it in
pyproject.toml. MIT is common for beginner
open-source packages.
CHANGELOG.md now, even with one entry, so
future version updates have a clear place to live.
# 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
from mypackage.cli import main
def test_main_runs(capsys):
main()
captured = capsys.readouterr()
assert "working" in captured.out
# Changelog
## 0.0.1
- First public package structure.
- Added CLI command.
- Added README, license, and install test.
Before uploading anywhere, build the package and validate the distribution metadata. This catches many README, metadata, and packaging errors before they become public.
dist/ files before every build so
you never upload stale wheels or source archives.
.tar.gz source archive and a
.whl wheel inside dist/.
twine check validates package metadata and
README rendering before the upload step.
python -m pip install --upgrade build twine pytest
python -m pytest -q
rm -rf dist
python -m build
python -m twine check dist/*
dist/
├── mypackage-0.0.1.tar.gz
└── mypackage-0.0.1-py3-none-any.whl
python -m pip install --force-reinstall dist/*.whl
python -c "import mypackage; print(mypackage.__name__)"
Successfully built ... and
Checking dist/...: PASSED.
python -m pip install --upgrade twine.
dist/ after tests, build, and
twine check all pass.
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.
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
git init again. Commit and push using
your existing repository setup.
TestPyPI lets you verify the publish process without making the package available on the real PyPI index.
test.pypi.org and create a TestPyPI
token. A real PyPI token will not work here.
dist/. Do not rebuild between
twine check and this upload.
__token__. Paste the token as the password.
python -m twine upload --repository testpypi dist/*
username: __token__
password: pypi-your-testpypi-token-here
https://test.pypi.org/project/mypackage/. Open it
and check the package name, version, README, author, and project
links.
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.
your-exact-package-name==0.0.1, when you want
a stricter verification.
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.
numpy, verify it
with python -c "import numpy". If your package
creates a CLI command such as np, verify it with
np --help.
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
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
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
--extra-index-url allows pip to install
dependencies from the real PyPI index while installing your
package from TestPyPI.
If TestPyPI looks correct and a clean install works, upload the same distribution files to PyPI.
dist/. If you rebuild, go back through tests,
build, twine check, and TestPyPI.
pypi.org, not
test.pypi.org. The two sites use separate
accounts and tokens.
0.0.1 is on PyPI, you cannot
replace that same version with changed files.
python -m twine upload dist/*
username: __token__
password: pypi-your-real-pypi-token-here
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.
Open the live project page and test installation from real PyPI. This is the final confirmation users can install your package.
python -m pip install your-exact-package-name.
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.
https://pypi.org/project/your-exact-package-name/
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
After the clean temporary install works, try the package inside a real project. This catches practical issues that a minimal test may miss.
cd path/to/real-project
python -m pip install your-exact-package-name
your-cli-command --help
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.
The first upload often needs an account-wide token because the project does not exist yet. After the first upload succeeds, remove that risk.
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.
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.
0.0.1 to 0.0.2
0.0.x to 0.1.0
1.0.0 only when the API and workflow
are stable enough for users to depend on.
A release is not only code. Update every file a user depends on to understand the new version.
# Changelog
## 0.0.2
- Add copy buttons to every command snippet.
- Expand TestPyPI and PyPI install verification steps.
- Improve token security guidance.
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.
0.0.1 was uploaded, use 0.0.2 or
another new version.
pyproject.toml,
__init__.py, README, and changelog.
dist/ should show
the new version number in their filenames.
[project]
name = "mypackage"
version = "0.0.2"
grep -R "0.0.1" pyproject.toml README.md CHANGELOG.md src tests
dist/, rebuild, and run
twine check again. The built files do not update
themselves.
The dist/ folder contains built artifacts. If you
forget to clear it, you may accidentally upload an old version.
dist/ is generated output. Removing it does not
delete your source code, tests, README, or project settings.
dist/. Clearing the
folder makes sure you upload only the new version.
dist/, build fresh files,
then run twine check.
python -m pytest -q
rm -rf dist
python -m build
python -m twine check dist/*
python -m pytest -q
Remove-Item -Recurse -Force dist
python -m build
python -m twine check dist/*
dist/
├── mypackage-0.0.2.tar.gz
└── mypackage-0.0.2-py3-none-any.whl
dist/:
stop, fix the version number, remove dist/ again,
rebuild, and rerun twine check.
Even after the first release, TestPyPI is still useful. It lets you catch packaging mistakes before the real PyPI release.
dist/ contains the new version, such as
0.0.2, before uploading.
twine check again.
python -m twine upload --repository testpypi dist/*
Use a fresh temporary environment and ask for the exact version. This confirms pip can install the release you just uploaded.
your-exact-package-name==0.0.2, so you know pip
found the update.
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.
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
==0.0.2.
When TestPyPI and the clean install test both pass, publish the same artifact files to the real PyPI index.
0.0.2 after upload. Problems require another
version bump.
python -m twine upload dist/*
python -m pip install --upgrade your-exact-package-name==0.0.2
your-cli-command --help
A Git tag makes it easy to find the exact source code for the package version users installed.
v0.0.2 for package version 0.0.2.
git add .
git commit -m "Release 0.0.2"
git tag v0.0.2
git push
git push origin v0.0.2
git add and git commit, then tag
the commit that produced the files you uploaded.
Open a GitHub issue and include the step number, command, and error message so the guide can improve for everyone.