Content from Why Python Packages?
Last updated on 2025-10-25 | Edit this page
Overview
Questions
- Why must we package our Python code?
- What precisely may be packaged?
- Why is __init_.py file important?
- What is the Python Package Index (PyPI), and what purpose does it serve?
- What is a build?
Objectives
- To understand the benefits of Python packaging.
- To determine whether packaging is suitable for your use case.
- To understand the importance of __init_.py file.
- To acquire knowledge of components of Python packaging and understand Python Package Index (PyPI).
- To become familiar with the build process.
Ref : https://xkcd.com/1987/
Introduction
Software development is a creative pursuit, and it is satisfying to write code that solves interesting problems. However, once our code is ready, how can we share it globally? This course provides instruction in how to distribute Python code using Pixi. You might liken a package to the old-style setup.exe: a standardised method of distributing software.
By packaging code, you address several important challenges, including ensuring reproducibility, achieving cross-platform compatibility, and supporting multiple environments. Distributing code as a package offers many advantages over merely sharing source files, for example via GitHub:
- Dependency management: A package can explicitly declare its dependencies. Tools such as pip (or uv) can then automatically install them for the user.
- Versioning: You may version your package and distribute multiple versions, thereby supporting backwards compatibility.
- Standardisation: Packaging is the established method of distributing code via recognised repositories such as PyPI and conda forge.
- Metadata: Packages include project-specific metadata, which is essential for end users.
Python Package Structure Hierarchy
- Function : A block of reusable code that performs a single specific task.
- Module : A single Python file (.py) that groups related classes, functions and variables.
- Package : A folder/ collection containing multiple modules and an init.py file to organize them.
- The init.py files are required to make Python treat folders containing the file as packages
- Library : A collection of related packages or modules that provide broad functionality for reuse.
- Library can have Package(s), which can have module(s) which can have function(s). It can also be considered a Project.
- Python package : A Python package is a collection of related code modules (files) bundled with metadata describing how the package should be installed and used *
Steps to create a Python Package :
What may be packaged ?
Any .py files (modules) or directories with init.py (packages) can be packaged.
What is init.py file in Python?
The init.py file (aka dunder init) is a Python file that is executed when a package is imported. It serves two main purposes:
It marks the directory as a Python Package so that the interpreter can find the modules inside it.
-
It can contain initialization code for the Package, such as importing submodules, defining variables, or executing other code.
What is PyPI
PyPI is the repository where all released Python packages are made available for end users. There is also TestPyPI, a repository which allows us to experiment with distribution tools and procedures without affecting the live PyPI. In this course, we will publish our package to TestPyPI. You can search PyPI for existing packages to use in your project instead of creating a new one. To make your package easily discoverable, it’s important to provide correct metadata and appropriate classifiers.
What is a Build ?
A build is the process by which your project’s source code is
transformed into a distributable format. These build artefacts can then
be installed using tools such as pip. A build may be
created using the command in the terminal :
bash python -m build The successful build process produces
files such as a wheel (.whl) or a
source archive (.tar.gz), which can be
installed via pip or uploaded to PyPI. It is vital to
version your build and supply the requisite metadata.
Please note: There are several tools and backends available for building Python packages.
- To make Python code installable, reusable and distributable via PyPI or TestPyPI, one must package the code.
- The Package should have modules (.py file(s)) and init.py file.
- The code must be versioned.
- Project dependencies must be managed.
- The project’s metadata must be clearly defined.
Content from Why choose Pixi for Python Packaging ?
Last updated on 2025-10-26 | Edit this page
Overview
Questions
- Why use Pixi?
- What are the benefits of Pixi ?
Objectives
- To understand the advantages offered by Pixi.
- To learn about
pixi.tomlandpyproject.toml - To understand the role of
pixi.lock. - To explore the concept of multi-environment support.
Introduction
Pixi is a fast, modern and reproducible package management tool. It has lots of features which are not all present in a single tool at this point in time. Read here. These capabilities make Pixi an attractive choice for managing Python and multi-language projects.
Key features include:
- Support for both PyPI and Conda packages: enabling flexibility in sourcing dependencies.
- Performance: lightweight and modern, designed for speed.
- Multi-language dependency management: e.g. Python with Rust, or Python with C/C++.
-
Integration with
uv: leveraging a high-performance package installer. -
Reproducibility: guaranteed through the use of
pixi.lock. -
Configuration via TOML files: supports both
pixi.tomlandpyproject.toml.
Configuration files (pixi.toml and
pyproject.toml)
pixi.toml: The configuration file used by Pixi to define
environments, dependencies, and tasks. pyproject.toml: A
standard configuration file within the Python ecosystem (PEP 518/621).
It is required by build tools such as Poetry, Hatch, Flit, and
setuptools. This file specifies project metadata (e.g. name, version,
author) as well as dependencies and build settings.
Multi-environment support
Pixi allows you to define dependencies for specific operating systems (e.g. Windows, macOS, Linux) or for distinct environments such as development, testing, and production. This makes it easier to tailor the project configuration to match the context in which the software is being deployed or developed.
pyproject.toml
For this demo, we will mainly focus on pyproject.toml
file. We make this choice due to following specifications and
recomendations : PEP
621, PEP 517, and PEP 660.
You can also read at these links:
envs
Pixi automatically manages project environments. When you initialize
the project with pixi init, it creates the project
configuration, and the environment is automatically set up when you
install dependencies or run tasks.
dependency management
When you add a package via Pixi
(pixi add <package>), it updates the project
configuration (pyproject.toml or pixi.toml)
and generates or updates the pixi.lock file, recording the
exact version and source to ensure reproducible environments.
- Choose a tool with good support and long term vision.
- Choose a tool suitable for your project.
- Focus on PEP specifications and recomendations.
Content from Set up a project directory
Last updated on 2025-10-26 | Edit this page
Overview
Questions
- How should we structure our project ?
- What is
__init__.py? - How should be name our packages ?
Objectives
- To understand how to structure a project effectively in order to package it successfully.
- To recognise the importance of the
__init__.pyfile and know where to place it. - To assign unique yet meaningful names to packages.
Introduction
It is essential to structure your project correctly, include the necessary files, and provide packages with names that are both unique and understandable.
Packages are used to organise modules within a Python project. As projects often consist of several modules, grouping them into packages helps to keep the codebase structured, maintainable, and easy to navigate.
Each package must contain a special file named
__init__.py. The presence of this file indicates to Python
that the folder is a package, thereby allowing it to be imported into
your code.
It is a recommended practice to place your Python packages inside a
src/ directory. This structure helps prevent accidental
imports from the working directory and ensures your tests accurately
reflect how your package will be used after installation.
Project Structure
A typical project would look like :
greet_me/
└── src/my_package/
├── __init__.py
├── happy.py
└── sad.py
Let us create a similar structure within our codespace.
OUTPUT
✔ Created /workspaces/pixi_demo/pyproject.toml
This generates the following structure:
pyproject.toml : The initial pyproject.toml
file generated may look like this:
TOML
[project]
authors = [{name = "Priyanka Demo", email = "demo@users.noreply.github.com"}]
dependencies = []
name = "greet_me"
requires-python = ">= 3.11"
version = "0.1.0"
[build-system]
build-backend = "hatchling.build"
requires = ["hatchling"]
[tool.pixi.workspace]
channels = ["conda-forge"] #Download and install Conda packages from the conda-forge channel
platforms = ["linux-64"]
[tool.pixi.pypi-dependencies] #Add dependencies here which needs to be installed from PyPI
greet_me = { path = ".", editable = true }
[tool.pixi.tasks]
To add libraries via Pixi use the pixi add command. Add
the requests library:
OUTPUT
✔ Added requests >=2.32.5,<3
This will create/update the [tool.pixi.dependencies]
section in pyproject.toml.
It also generates a pixi.lock file which may look
somewhat like the image below.
To remove a package, use this command and check that
pyproject.toml is corrected and the package is removed from
there.
OUTPUT
✔ Removed requests
To add libraries from PyPI via Pixi:
OUTPUT
✔ Added requests >=2.32.5, <3
Added these as pypi-dependencies.
Check the pyproject.toml file. These get added under the
[project] section
TOML
[project]
authors = [{name = "Priyanka Demo", email = "demo@users.noreply.github.com"}]
dependencies = ["requests>=2.32.5,<3"]
name = "greet_me"
Other commands, that can be later explored :
pixi lock : Generates or updates the
pixi.lock file by resolving exact package versions for
reproducible environments. This is conceptually similar to
pip freeze, but instead of listing installed packages, it
proactively locks dependency versions.
OUTPUT
✔ Lock-file was already up-to-date
pixi install : Creates or updates the project
environment based on your configuration files (pixi.toml /
pyproject.toml and pixi.lock). Useful to run
after cloning a repository.
OUTPUT
✔ The default environment has been installed.
pixi update : It refreshes and upgrades dependencies in
your Pixi project to their latest compatible versions, updating both the
environment and the lock file.
OUTPUT
▪ solving [━━━━━━━━━━━━━━━━━━━━] 0/1
▪ updating lock-files [━━━━━━━━━━━━━━━━━━━━] 0/2
OUTPUT
✔ Lock-file was already up-to-date
pixi tree : It shows a dependency tree for your current
Pixi project or environment
OUTPUT
├── requests 2.32.5
│ ├── charset_normalizer 3.4.4
│ ├── idna 3.11
│ ├── urllib3 2.5.0
│ └── certifi 2025.10.5
├── python 3.14.0
│ ├── bzip2 1.0.8
│ │ └── libgcc 15.2.0
...
Adding Modules
Lets create these 2 files: happy.py, sad.py
in the folder src/greet_me.
Please note, in VSCode inside GitHub codepsaces, you mabe be prompted
for below, when creating a python file. So just Install it.
The overall project structure should then resemble:
greet_me/
├── .pixi
├── .gitattributes
├── .gitignore
├── LICENSE
├── pyproject.toml
├── pixi.lock # auto-generated, do not edit
└── src/greet_me/
├── __init__.py
├── happy.py
└── sad.py
Running a Task
task is a command alias that you can execute easily via
the CLI (e.g., pixi run task-name).
Run the following command to add a task and observe the changes in
pyproject.toml file:
pyproject.toml file:
Then execute:
OUTPUT
Yay! happy day! 😀
You can read more about tasks here, which contains all the advanced use cases needed in a professional setting.
- Follow the appropriate folder structure.
- Always include the
__init__.pyfile in packages. - Sequence of Pixi commands: init → add → run → remove → lock → install → update.
- Define / check
[project],[dependencies]and[tasks]in yourpyproject.tomlfile. - Keep your project dependencies lean and remove unused packages.
Content from Metadata for Python packging
Last updated on 2025-10-27 | Edit this page
Overview
Questions
- What is
pyproject.toml? - What is a lockfile, and why is it necessary?
Objectives
- To understand the structure and purpose of
pyproject.toml - To appreciate the role and necessity of a lockfile.
Introduction
In the previous lesson, we briefly looked into
pyproject.toml. In this lesson, we shall focus a bit more
in detail on pyproject.toml, which is the most widely used
configuration format for Python projects. Please note, for projects
managed with Pixi, either pyproject.toml or
pixi.toml may be employed. The primary distinction lies in
syntax, so you need only ensure that you follow the appropriate
conventions.
The [build-system] table
This section of a pyproject.toml file informs packaging
tools such as pip which software is required to build your
project. It specifies the build backend responsible for
producing distributable packages such as wheels (.whl) or
source distributions (.sdist).
This section was introduced by PEP 518 and is essential for modern Python packaging. It has two main keys:
-
requires: A list of packages required to build the project. These are downloaded and installed into a temporary, isolated environment prior to the build process. The build backend itself must also be listed here. -
build-backend: A string reference to the Python object (the “backend”) that will be invoked by packaging tools to create the distributable packages.
Some other build tools to read are:
pdm.backend e.g. fastapi
-
setuptools.build_meta e.g. parselmouth
Also, refer to the table here
Editable Installation
Projects may be installed in editable mode, which allows you to make
changes to the source code and have them reflected immediately in the
environment without reinstallation. For example, the
greet_me package we are creating is listed as editable by
default.
Dependency Management
For dependency handling, the pyproject.toml file should include the requires-python field, which specifies the supported Python versions. For example:
Additional sections in pyproject.toml may include:
-
[tool.pixi.workspace]: Defines project-wide settings, including package sources and target platforms for resolution.
-
[tool.pixi.pypi-dependencies]: Declares the dependencies to be installed from PyPI (or equivalent sources). These are the external libraries required by the project.
You can specify a range or multiple supported Python versions using the syntax below.
Classifiers
They are standardized metadata tags that describe your Python package for PyPI and help with filtering and discoverability. You can look for the list of acceptable classifiers here. They are defined under project section.
TOML
[project]
...
classifiers = [
"Programming Language :: Python :: 3",
"License :: OSI Approved :: MIT License",
"Operating System :: OS Independent",
]
...
Final pyproject.toml should look like this below, for
reference.
TOML
[project]
authors = [{name = "Priyanka Demo", email = "demo@users.noreply.github.com"}]
dependencies = []
name = "greet_me"
requires-python = ">= 3.11"
version = "0.1.0"
description = "greet_me Pixi-managed package"
readme = "README.md"
[build-system]
build-backend = "hatchling.build"
requires = ["hatchling"]
[tool.pixi.workspace]
channels = ["conda-forge"]
platforms = ["linux-64"]
[tool.pixi.pypi-dependencies]
requests = ">=2.32.5,<3"
greet_me = { path = ".", editable = true }
[tool.pixi.tasks]
start = "python -c 'from greet_me import happy; print(happy.greet_happy())'"
For insipiration, also check here.
You can read more about pyproject.toml here.
Lockfiles
A lockfile contains the complete set of
dependencies, including specific versions, required to reproduce the
project environment. It is automatically generated based on the
dependencies listed in the .toml file, ensuring that builds
remain consistent and reproducible.
Readme
Please add and update the README.md file, in case you havent done so. You can easily generate a README text on readme.so and copy its content to your READMe file.
- Every project must include a
pyproject.tomlfile - The
[build-system]section is required and must define bothrequiresandbuild-backend. - The
[project]section must, at minimum, include the projectnameandversion. - It is recommended to specify dependencies in the
[project]section for clarity and reproducibility. - Use semantic versioning (MAJOR.MINOR.PATCH) for versioning your packages.
Content from 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.
Content from Extra
Last updated on 2025-10-30 | Edit this page
Overview
Questions
- How can we use
pixi run start? - How to yank or un-yank a release?
Objectives
- Learn how to use Pixi to run your project.
- Learn how to make a release unavialable or undo that.
Introduction
After cloning a project, Install Pixi and Pixi makes it simple to run
predefined tasks. If your pixi.toml (or
pyproject.toml) contains a task named start, you can
execute it directly using:
If required, restart your shell:
Verify that Pixi has been installed correctly.
Now run
OUTPUT
✨ Pixi task (start): python -c 'from greet_me1 import happy; print(happy.greet_happy())'
Yay! happy day! 😀
This command will:
- Ensure that the required environment is installed (creating or updating it if necessary).
- Run the start task exactly as defined in your configuration file.
This provides a convenient and reproducible way to launch your project without needing to manually manage dependencies or commands. You can check the example project here
Yank and Un-yank
Occasionally, a release may contain an error or be uploaded by
mistake. While PyPI and TestPyPI don’t allow deleting releases for
security and reproducibility reasons, you can mark a specific version as
yanked. Yanked releases remain accessible for reproducibility but are
ignored by default when users install packages with
pip install <package-name>.
Steps to Yank a version of your Python Package:
Log into your PyPI or TestPyPI account
Click on the your Projects from the top right location under your user-name.
Select the right project /package from the options shown and click on the Manage button.
Select the version you wish to yank and choose that option by clicking on the Options button and select Yank.
You will be shown a pop-up. Fill the version nos and your releace will not be yanked.
Once your release is yanked, it be be shown like below.
You can also un-yank it, by clicking on the Options and clicking on Un-yank.
When you do a
pip installwithout stating an explicit verion and no un-yanked versions are available. You may get an error as shown below :
You can read more about Yanking here and related PEP 592 specification here.
Further Reading
- Define tasks such as
startin yourpixi.tomlorpyproject.toml. - Use
pixi run <task-name>to execute those tasks. -
pixi run startensures consistency and reproducibility when launching a project. - Yank a faulty release and provide useful comments for why you yanked it.
