How to publish your Python project
Last updated on 2025-10-22 | Edit this page
Overview
Questions
- What is Twine and why is it needed
- What is
build
command 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
build
andtwine
. - 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.
Note: Before proceeding, rename or remove the
pixi.tom
l file, as we will focus on
pyproject.toml
. You may experiment with
pixi.toml
later by removing or renaming
pyproject.toml
.
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.
Empty the depenencies under [project]
and move/edit them
to [tool.pixi.pypi-dependencies]
section, like shown below
:
TOML
[project]
dependencies = []
[tool.pixi.pypi-dependencies]
requests = ">=2.32.5,<3"
build = ">=1.3.0,<2"
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
and modify the
pyroject.toml
file simmilar to what you did for
build
tool above.
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_me1-0.1.7-py3-none-any.whl
100% ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 4.3/4.3 kB • 00:00 • ?
Uploading greet_me1-0.1.7.tar.gz
100% ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 12.2/12.2 kB • 00:00 • ?
View at:
https://test.pypi.org/project/greet-me1/0.1.7/
After a successful upload, your package will be available at a URL
such as: E.g. :
[https://test.pypi.org/project/po-greet-me/0.1.1/](https://test.pypi.org/project/greet-me1/0.1.7/)
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.1-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 isnt 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
) and also change this
section, [tool.pixi.pypi-dependencies]
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 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
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
build
to generate distribution files - Create a TestPyPI account and generate an API token
- Use
twine upload
to securely publish your package. - Test your package by installing it from TestPyPI via
pip install
.