Skip to content

Commit 774d9d0

Browse files
committed
📚 Publishing: add first full draft of page
1 parent d0b4af3 commit 774d9d0

1 file changed

Lines changed: 202 additions & 5 deletions

File tree

‎docs/publishing.md‎

Lines changed: 202 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -38,11 +38,208 @@ This creates a network of packages that work together seamlessly.
3838

3939
## How
4040

41-
Publishing involves:
41+
### Building your package
4242

43-
- Building your package into distribution formats (typically a wheel `.whl` and source distribution `.tar.gz`).
44-
- Uploading these distributions to PyPI.
45-
- Making your package discoverable and installable by anyone in the Python community.
43+
Before you can publish your package, you need to build it into distribution formats that `pip` can install.
44+
The two standard formats are:
4645

47-
In the days of old, you may have executed these steps manually.
46+
- **Wheel (`.whl`)**: A pre-built binary distribution that installs quickly
47+
- **Source distribution (`.tar.gz`)**: The raw source code that gets built during installation
4848

49+
To create these distributions, you need a **build backend**.
50+
The workshop template uses [Hatchling](https://hatch.pypa.io/), a modern, standards-compliant build system.
51+
You can see this configured in your `pyproject.toml`:
52+
53+
```toml {.no-copy}
54+
[build-system]
55+
requires = ["hatchling"]
56+
build-backend = "hatchling.build"
57+
```
58+
59+
This tells Python's build tools to use Hatchling when building your package.
60+
61+
To build your package, use the `build` module (part of the Python Packaging Authority's standard tooling):
62+
63+
```bash
64+
pip install build
65+
python -m build
66+
```
67+
68+
```console {.no-copy}
69+
* Creating isolated environment: venv+pip...
70+
* Installing packages in isolated environment:
71+
- hatchling
72+
* Getting build dependencies for sdist...
73+
* Building sdist...
74+
* Building wheel from sdist...
75+
* Creating isolated environment: venv+pip...
76+
* Installing packages in isolated environment:
77+
- hatchling
78+
* Getting build dependencies for wheel...
79+
* Building wheel...
80+
Successfully built dev-tutorial-<YOUR_USERNAME>-0.0.1.tar.gz and dev_tutorial_<YOUR_USERNAME>-0.0.1-py3-none-any.whl
81+
```
82+
83+
This creates a `dist/` directory containing both distribution formats:
84+
85+
```bash
86+
ls dist/
87+
```
88+
89+
```console {.no-copy}
90+
dev-tutorial-<YOUR_USERNAME>-0.0.1.tar.gz
91+
dev_tutorial_<YOUR_USERNAME>-0.0.1-py3-none-any.whl
92+
```
93+
94+
### Versioning
95+
96+
Notice that the built distributions include a version number (`0.0.1`).
97+
This comes from the `__version__` variable in `src/dev_tutorial_<YOUR_USERNAME>/__about__.py`:
98+
99+
```python {.no-copy}
100+
__version__ = "0.0.1"
101+
```
102+
103+
Version numbers help users understand what changes between releases.
104+
The standard approach is [Semantic Versioning (SemVer)](https://semver.org/), which uses the format `MAJOR.MINOR.PATCH`:
105+
106+
- **MAJOR**: Increment when you make backwards-incompatible API changes (e.g., `1.0.0` → `2.0.0`)
107+
- **MINOR**: Increment when you add functionality in a backward-compatible manner (e.g., `1.0.0` → `1.1.0`)
108+
- **PATCH**: Increment when you make backward-compatible bug fixes (e.g., `1.0.0` → `1.0.1`)
109+
110+
For example:
111+
- `0.0.1` → `0.0.2`: Fixed a bug
112+
- `0.0.2` → `0.1.0`: Added a new feature
113+
- `0.1.0` → `1.0.0`: First stable release or breaking changes
114+
115+
!!!info "Version 0.x.x"
116+
117+
Versions starting with `0.` (like `0.1.0`) indicate the package is still in early development.
118+
Users should expect breaking changes between minor versions.
119+
Once your API is stable, release version `1.0.0`.
120+
121+
### Creating API tokens
122+
123+
Before you can upload to PyPI or Test PyPI, you need to create an **API token** for authentication.
124+
PyPI no longer accepts username/password authentication - tokens are required.
125+
126+
For Test PyPI:
127+
128+
1. Create an account at [https://test.pypi.org/](https://test.pypi.org/)
129+
2. Go to Account Settings → API tokens
130+
3. Click "Add API token"
131+
4. Give it a name (e.g., "dev-tutorial upload")
132+
5. Copy the token immediately - it's only shown once!
133+
134+
The token will look like `pypi-...` (a long string of characters).
135+
136+
!!!warning "Save your token!"
137+
138+
API tokens are only displayed once when created.
139+
Store it somewhere safe - you'll need it for uploads.
140+
If you lose it, you'll have to create a new one.
141+
142+
### Uploading to Test PyPI
143+
144+
Before publishing to the real PyPI, it's smart to test with **Test PyPI** - a separate instance where you can experiment without consequences.
145+
146+
Install `twine`, the tool for uploading packages:
147+
148+
```bash
149+
pip install twine
150+
```
151+
152+
Upload your distributions to Test PyPI:
153+
154+
```bash
155+
twine upload --repository testpypi dist/*
156+
```
157+
158+
When prompted:
159+
- **Username**: Enter `__token__` (exactly as written, with double underscores)
160+
- **Password**: Paste your API token (including the `pypi-` prefix)
161+
162+
Once uploaded, your package will be available at:
163+
164+
```
165+
https://test.pypi.org/project/dev-tutorial-<YOUR_USERNAME>/
166+
```
167+
168+
To install from Test PyPI and verify it works:
169+
170+
```bash
171+
pip install --index-url https://test.pypi.org/simple/ dev-tutorial-<YOUR_USERNAME>
172+
```
173+
174+
!!! info "The real PyPI"
175+
176+
Test PyPI is a separate service from the real PyPI.
177+
Packages uploaded there are periodically deleted, and dependencies from the real PyPI might not be available.
178+
It's purely for testing the upload process!
179+
Publishing to the real PyPI is very similar, but I would only do it with _your_ proper package when it's ready.
180+
181+
### Automating with GitHub Actions
182+
183+
Manually building and uploading packages works, but it's error-prone and tedious.
184+
A better approach is to automate the entire publishing process with GitHub Actions.
185+
The workshop template includes a workflow file `.github/workflows/cd.yml` that automatically publishes to Test PyPI when you push a version tag.
186+
187+
!!! question "If you are feeling lost"
188+
189+
I know this all seems like a lot. It is!
190+
The main goal here is to expose you to this approach of doing things, you can take your time in understanding all the aspects.
191+
192+
### Setting up trusted publishing
193+
194+
Instead of storing API tokens as secrets, the workflow uses **trusted publishing** - a modern, more secure authentication method.
195+
With trusted publishing, PyPI trusts GitHub Actions to publish on your behalf without needing to manage tokens.
196+
197+
To set this up on Test PyPI:
198+
199+
1. Go to [https://test.pypi.org/](https://test.pypi.org/) and log in
200+
2. Click **Your projects** (but you won't have a project yet - that's okay!)
201+
3. Go to **Publishing** in the left sidebar
202+
4. Scroll to **Add a new pending publisher**
203+
5. Fill in the form:
204+
- **PyPI Project Name**: `dev-tutorial-<YOUR_USERNAME>` (your package name)
205+
- **Owner**: Your GitHub username
206+
- **Repository name**: `dev-tutorial-<YOUR_USERNAME>`
207+
- **Workflow name**: `cd.yml`
208+
6. Click **Add**
209+
210+
!!!info "How trusted publishing works"
211+
212+
When your GitHub Actions workflow runs, GitHub issues a short-lived identity token that proves the workflow is running from your specific repository.
213+
Test PyPI verifies this token and allows the upload - no long-lived secrets needed!
214+
This is more secure because there are no tokens to leak or rotate.
215+
216+
### Automated publishing workflow
217+
218+
With trusted publishing and automated workflows, releasing a new version is as simple as:
219+
220+
1. Update the version in `__about__.py` following SemVer (e.g., `0.0.1` → `0.1.0`)
221+
2. Commit the bump in version number:
222+
223+
```
224+
# Update version, commit, tag, and push
225+
git commit -am 'Bump version to 0.1.0'
226+
```
227+
228+
3. Create and push a Git tag:
229+
230+
```
231+
git tag v0.1.0
232+
git push origin main v0.1.0
233+
```
234+
235+
→ **GitHub Actions automatically builds and publishes to Test PyPI!**
236+
237+
Watch the workflow run in the **Actions** tab of your repository.
238+
If something goes wrong, check the workflow logs for error messages.
239+
240+
Once published, verify your package has a new version at `https://test.pypi.org/project/dev-tutorial-<YOUR_USERNAME>/`.
241+
242+
!!! info "This is the end..."
243+
244+
For now, this is the end of this basics of software development workshop.
245+
More material might be coming soon!

0 commit comments

Comments
 (0)