How to publish your Python project
Last updated on 2025-10-26 | Edit this page
Overview
Questions
- What is Twine and why is it needed
- What is
buildcommand and what does it do ? - How can we create and upload a Python package?
- How can we test and use the uploaded Python package?
Objectives
- Learn how to use tools such as
buildandtwine. - Understand how to upload a Python package to repositories such as TestPyPI.
- Learn how to install and test an uploaded package.
Introduction
Once a project has been created and all the necessary metadata has been defined in the TOML file, the next step is to publish it. This lesson introduces the tools and steps required to achieve this.
The two key tools we need are:
Build – for generating distribution files from your project.
Twine – for securely uploading those distributions to PyPI or TestPyPI.
Step 1: Create your build
The build tool reads your pyproject.toml
file and generates the package distribution files.
OUTPUT
✔ Added build >=1.3.0, <2
Added these as pypi-dependencies.
Now run the build command:
OUTPUT
* Creating isolated environment: venv+pip...
* Installing packages in isolated environment:
- hatchling
* Getting build dependencies for sdist...
* Building sdist...
* Building wheel from sdist
* Creating isolated environment: venv+pip...
* Installing packages in isolated environment:
- hatchling
* Getting build dependencies for wheel...
* Building wheel...
Successfully built greet_me-0.1.0.tar.gz and greet_me-0.1.0-py3-none-any.whl
This command creates a dist directory containing two
files:
- A wheel file (
greet_me-0.1.0-py3-none-any.whl). - A source archive (
greet_me-0.1.0.tar.gz).
Step 2 : Create an account on TestPyPI
- Visit TestPyPI and create an account.
- Generate an API token from your account settings.
- Save the token securely, as you will use it during the upload process.
Step 3: Upload your build
The Twine tool is used to securely upload your
package distributions. Install twine.
TOML
[project]
dependencies = []
[tool.pixi.pypi-dependencies]
requests = ">=2.32.5,<3"
build = ">=1.3.0,<2"
Now upload the package to TestPyI
You will be prompted for your TestPyPI API Token, so keep it handy
and paste it, when prompted. Use the API token instead of your password
by entering __token__ as the username and pasting the token
when prompted for a password.
OUTPUT
Uploading distributions to https://test.pypi.org/legacy/
WARNING This environment is not supported for trusted publishing
Enter your API token:
Uploading greet_me-0.1.0-py3-none-any.whl
100% ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 4.3/4.3 kB • 00:00 • ?
Uploading greet_me-0.1.0.tar.gz
100% ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 12.2/12.2 kB • 00:00 • ?
View at:
https://test.pypi.org/project/greet-me/0.1.0/
After a successful upload, your package will be available at a URL such as: E.g. : https://test.pypi.org/project/greet-me/0.1.0/.
Handling Errors
If the package name is too similar to an existing project, TestPyPI may return a 403 Forbidden or 400 Bad Request error. In that case you may end up in an error like this :
OUTPUT
twine upload --repository testpypi dist/*
Uploading distributions to https://test.pypi.org/legacy/
Enter your API token:
Uploading greet_me-0.1.0-py3-none-any.whl
100% ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 2.6/2.6 kB • 00:00 • ?
WARNING Error during upload. Retry with the --verbose option for more details.
ERROR HTTPError: 403 Forbidden from https://test.pypi.org/legacy/
Forbidden
This isn’t always helpful and you should try this command as tipped in the error message above to know more.
OUTPUT
Uploading distributions to https://test.pypi.org/legacy/
INFO dist/greet_me-0.1.7-py3-none-any.whl (2.3 KB)
INFO dist/greet_me-0.1.7.tar.gz (10.7 KB)
INFO username set by command options
INFO Querying keyring for password
INFO No keyring backend found
INFO Trying to use trusted publishing (no token was explicitly provided)
WARNING This environment is not supported for trusted publishing
Enter your API token:
INFO username: __token__
INFO password: <hidden>
Uploading greet_me-0.1.7-py3-none-any.whl
100% ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 4.2/4.2 kB • 00:00 • ?
INFO Response from https://test.pypi.org/legacy/:
400 Bad Request
INFO <html>
<head>
<title>400 The name 'greet-me' is too similar to an existing project. See
https://test.pypi.org/help/#project-name for more information.</title>
</head>
<body>
<h1>400 The name 'greet-me' is too similar to an existing project. See
https://test.pypi.org/help/#project-name for more information.</h1>
The server could not comply with the request since it is either malformed or otherwise incorrect.<br/><br/>
The name 'greet-me' is too similar to an existing project. See
https://test.pypi.org/help/#project-name for more information.
</body>
</html>
ERROR HTTPError: 400 Bad Request from https://test.pypi.org/legacy/
Bad Request
To fix this, - Rename your Package Folder, [project].name (e.g. from
greet_me to greet_me1) - Make changes to your
package name in the pyproject.toml file.
TOML
[project]
name = "greet-me1" # Changed
[tool.pixi.pypi-dependencies]
requests = ">=2.32.5,<3"
build = ">=1.3.0,<2"
twine = ">=6.2.0,<7"
greet_me1 = { path = ".", editable = true } # Changed
[tool.pixi.tasks]
start = "python -c 'from greet_me1 import happy; print(happy.greet_happy())'" # Changed
- Rebuild and upload the package.
Step 4: Test your package
Create a new repository with a readme file, open a new codespace, and install your package from TestPyPI via this command:
OUTPUT
Looking in indexes: https://test.pypi.org/simple/
Collecting greet-me1==0.1.7
Downloading https://test-files.pythonhosted.org/packages/92/84/76afd107870f18a144fe5df0cc9fd0d7698c69e82e4c085f2ba339f99218/greet_me1-0.1.7-py3-none-any.whl.metadata (304 bytes)
Downloading https://test-files.pythonhosted.org/packages/92/84/76afd107870f18a144fe5df0cc9fd0d7698c69e82e4c085f2ba339f99218/greet_me1-0.1.7-py3-none-any.whl (2.4 kB)
Installing collected packages: greet-me1
Successfully installed greet-me1-0.1.7
In case you get an error like this :
OUTPUT
ERROR: Could not find a version that satisfies the requirement requests<3,>=2.32.5 (from pixi-demo1) (from versions: 2.5.4.1)
ERROR: No matching distribution found for requests<3,>=2.32.5
resolve it by adding this extra parameter
--extra-index-url https://pypi.org/simple/ to your
pip install command. This tells the pip command to also
look for packages in the extra URL provided.
BASH
pip install -i https://test.pypi.org/simple/ --extra-index-url https://pypi.org/simple/ greet-me1==0.1.0
Then create a test script : test_package.py:
Run it:
OUTPUT
Yay! happy day! 😀
- Ensure all metadata is filled in and choose a unique project name.
- Use
buildto generate distribution files - Create a TestPyPI account and generate an API token
- Use
twine uploadto securely publish your package. - Test your package by installing it from TestPyPI via
pip install.
