There are three main stages of this tutorial:
- Initialize example project
- Upload example project to Test PYPI
- Package and release the example project using CD provided by GitHub
There will be instructions for each of the stages. For each of the steps that include modification on the git repository, an example will be given inside the commits describing those steps. I won't mention explicitly where should you commit your changes. That is up to you. The only places when that is required are when making a release with a tag.
Here are the steps you should follow at this stage of the tutorial:
- Choose a name for your new GitHub project. Give it a name that can be used as a
unique identifier in a global scope (that is if you want your python project
name to be the same as your GitHub project title). For example, you can use
the format I used:
oss-eu-2022-tutorial-<YOUR_GITHUB_USERNAME>
- Create a public GitHub project with the name of your choice. It should
contain a
README.md
andLICENCE
files. - Clone the repository locally.
- Create a virtual environment with the python version of your choice.
- Add a
.gitignore
file and add a line in it ignoring the virtual environment folder. - Create the directories
src/<GITHUB_PROJECT_NAME>
and in it create an__init__.py
and a simple source file. - Create
pyproject.toml
configuration file and populate it the way suggested in Packaging Python Projects — Python Packaging User Guide: Configuring metadata. - Make sure that inside
pyproject.toml
you will update:
name
andautors
fields under theproject
section. Note:name
andGITHUB_PROJECT_NAME
must be the same.- the
project.urls
section with the actual GitHub project URLs. - the
licence
field tolicence = { text = { <LICENCE_NAME> }
where the<LICENCE_NAME>
should be the name of your licence. This is done so that your project license field can look better on Test PyPI. - the
build-system
section by pinninghatchling
version in therequires
with the version you are going to use. This is important for reproducibility reasons. - add the
tool.hatch.build.targets.sdist
section with theinclude
field where you will describe which files and folders will be included in the build artifacts. An example of all of those changes is included in the commit containing this step.
- Add a short explanation to your README.
At this stage, you are going to build your python project and upload it to Test PyPI. Here are the steps you need to follow:
- Add a
requirements-build.txt
file containing the requirements needed to build and upload our project. You can see an example in the commit containing that change. - Install the build dependencies from
requirements-build.txt
withpip install -r requirements-build.txt
. - Install
twine
withpip install --upgrade twine
. - Generate distribution archives by building your project with
python3 -m build
It's possible that you will have to installvirtualenv
withpip install virtualenv
. - Add the
dist
directory to.gitignore
. - Create an account on Test PyPI.
- Upload your project to Test PyPI with
python3 -m twine upload --repository testpypi dist/*
- You can verify that your project was indeed uploaded to Test PyPI by looking
into your profile and installing it with:
python3 -m pip install --index-url https://test.pypi.org/simple/ --no-deps <PROJECT_NAME>
.
This is the final stage where a couple of experiments will be performed and upon completion you will have a method to securely build and release your project through GitHub Continuous Deployment. Here are the steps you will need to do:
- You will have to generate a token responsible exclusively for your new project.
This is done so that you don't have to share your Test PyPI password with
GitHub. You can generate a new token by going on Test PyPI and clicking
<YOUR_USERNAME>
->Your Projects
->left of your project name "Manage"
->Settings
->Generate token for <PROJECT_NAME>
. You want to make sure you click on theScope
menu that the token is responsible only for your project. - When you generate the new token SAVE it somewhere as it will be needed later.
- Create the following directories:
.github/workflows
. - Inside
.github/workflows
create therelease.yml
file. - Add your first GitHub job inside
release.yml
. An example is available in the commit containing this step. - To activate the workflow run you will have to make a release. You can do this
by updating the
version
field inpyproject.toml
. - Tag your local project with git tag
<TAG_NAME>" -m <TAG_MESSAGE>
whereTAG_NAME
MUST start with av
. - Push the changes to GitHub.
- Push the tags by adding by running
git push
and adding the--tags
option. - View the last workflow that was automatically triggered by going to your project
GitHub page and the click
Actions
-> click the name of your workflow and verify that the output is as expected. - You will add steps to setup python, install your build dependencies, build your project and list the sha256 sums of the resulting artifacts. The new changes are in the commit containing this change.
- Make a new release and push it by following steps
6
to9
. - View the last workflow that was automatically triggered. Then click on the
build
job and then click theList shas
step. Next build your project locally, list the sha256 sums of the newly generated build files and verify that locally listed sha256 sums are the same sha256 sums as the ones listed on GitHub. - For the next steps, you will need an
environment
for your project. You can create one by going to your project's GitHub page, clickingSettings
->Environments
->New environment
and give whatever name you want. - Add a secret to the environment. You have to add the newly created token
responsible for your project which you created at
Upload the project to Test PyPI
stage step1
. The name of that secret has strict rules, so I suggest you just call itPYPI_TOKEN
. By making the secret bound to the environment this means that the access rules to that secret are limited by the environment and the secret cannot be easily retrieved. - You have to make sure that not all GitHub branches have access to your
secret. Go to the GitHub page of your project -> click
Settings
->Environments
-> click the name of your environment -> underDeployment branches
click theAll branches
button and then chooseSelected branches
-> clickAdd deployment branch rule
-> in theBranch name pattern
box writev*
. This makes sure that only branches/tags starting with the namev
can use the environmental secrets. - On the same page under
Environment protection rules
clickRequired reviewers
and in the text field add your GitHub username and clickSave protection rules
. This will make sure that before a GitHub workflow has access to your environmental secrets you will have to manually approve that. - In the
release.yml
file add a new step in thebuild
job making the output from the workflow available to other jobs. - Add a new
upload
job with steps toFetch build artifacts
andPublish on PyPi
. Note: An example of the changes mentioned in steps 17 and 18 can be seen in the commit adding those steps. - Make a new release and push it by following steps
6
to9
. - The workflow will be automatically triggered. After it finishes, verify
the sha256 sums (as described in step
12
) and if everything is correct at your workflow page you will see thatupload
is orange, click it -> chooseReview pending deployments
-> clickRelease
-> clickApprove and deploy
. Now you can verify everything is as expected by going to Test PyPI.
You can go a step further and introduce signing and signature verification of your releases. That will significantly improve the security of your project release processes. We have made the necessary changes introducing signatures and signing in the last 5 commits. Here are the steps you need to do in order to achieve that:
- Add
sigstore
to yourrequirements-build.txt
file. - Add the utility script we have added in the repository named
release
. Note: you can usesigstore
manually as a CLI tool, but for the purpose of simplifying this tutorial we are going to use therelease
script. - Create a
signatures
directory at the root of your project. - Open
pyproject.toml
and add/release
in thetool.hatch.build.targets.sdist
section. - Open
.github/workflows/release.yml
file and change theList shas
step in thebuild
job to theVerify release signature
step as shown in the previous commits. - Open
pyproject.toml
and bump theversion
attribute. - Build your project with
python3 -m build
- Sign your project by executing from the root folder:
./release sign {VERSION}
whereVERSION
is the incremented version insidepyproject.toml
. - Prepare all changes to be committed by adding them with
git add
. - Commit all changes.
- Push the changes
- Tag the changes. Remember the tag name MUST start with
v
. - Push the tags.
- Go to your project's GitHub page. The workflow should be automatically
triggered and the
build
job should complete successfully. You can approve theupload
job and release the next version of your project.
This is all I wanted to show you in this tutorial!
This tutorial was developed with the great help of Jussi Kukkonen and Joshua Lock!