I published my djent MIDI generator to PyPI as djenerator. At least, that was the plan. The name was already taken by an unrelated Django package for generating test data, so pip install djenerator gave people something completely different. The project became djentgen, and I learned to check names before anything else.
This tutorial is everything I'd tell myself before publishing that first Python package. We'll build notelen, a small CLI for musicians and producers that prints note lengths and delay times in milliseconds for any tempo:
$ notelen 120
note straight dotted triplet (120 bpm)
1/1 2000.0 3000.0 1333.3
1/2 1000.0 1500.0 666.7
1/4 500.0 750.0 333.3
1/8 250.0 375.0 166.7
1/16 125.0 187.5 83.3
1/32 62.5 93.8 41.7
It's small enough to read in one sitting, but the packaging setup is exactly what I use for djentgen.
Step 0: check the name
Do this before writing any code. Open https://pypi.org/project/your-name/ in a browser, or check from a terminal:
curl -s -o /dev/null -w "%{http_code}\n" https://pypi.org/pypi/notelen/json
A 404 means the name is free. A 200 means it's taken.
PyPI normalises names: case, hyphens, underscores and dots are all treated the same. So My_Tool, my-tool and my.tool are one name. If any of those exist, yours is taken. PyPI also blocks names that are too similar to popular packages, to stop typo-squatting.
Pick a name that's easy to type. People install your package from memory.
djentgenis fine;djent-midi-pattern-generator-pyisn't.
Step 1: one file to describe the project
Old Python packages used setup.py, setup.cfg, MANIFEST.in and sometimes requirements.txt all at once. Modern packaging needs one file: pyproject.toml. It tells tools two things:
- How to build the package (the
[build-system]table) - What the package is (the
[project]table: name, version, dependencies, commands)
Every modern tool reads it: pip, build, uv, Poetry, Hatch and PyPI itself.
djentgen still has a setup.py, but only as a two-line shim for old tools. All its real metadata lives in pyproject.toml.
Step 2: choose a layout
There are two common layouts.
Flat layout, where the package sits at the project root. This is what djentgen uses:
djentgen/
djentgen/
__init__.py
generator.py
pyproject.toml
src layout, where the package lives inside a src/ folder:
notelen/
src/
notelen/
__init__.py
tests/
pyproject.toml
We'll use the src layout for notelen, and it's what I'd recommend for new projects. With a flat layout, running your tests from the project root can import your local files directly, even if your packaging is broken. With src layout, Python can only import the package once it's installed, so your tests check what users will actually get.
Step 3: set up the project
Create the folders and a virtual environment:
mkdir -p notelen/src/notelen notelen/tests
cd notelen
python3 -m venv .venv
source .venv/bin/activate
Always work inside a virtual environment. It keeps this project's dependencies separate from everything else on your machine, and it means "it works on my machine" also means "it works in a clean environment".
Step 4: create your PyPI accounts
You'll need two accounts, both free:
They're completely separate, so you register twice. Both require two-factor authentication before you can publish.
What's next
You now have a free name, an empty project and somewhere to publish it. In the full tutorial we'll build and ship notelen:
- the complete CLI and its
pyproject.toml, line by line - a
notelencommand, pluspython -m notelensupport - a version number defined in exactly one place
- tests with pytest
- building and inspecting wheels and source distributions
- a practice run on TestPyPI
- publishing to PyPI from GitHub Actions with trusted publishing, so there are no API tokens to leak
Checking access…