visit
When I was creating my first Python package, dataclass-sqlalchemy-mixins
(github or ), I encountered an interesting challenge:
how to set up CI/CD
on GitHub to ensure that nothing breaks when I push new changes and to automatically publish the code to PyPI
. I discussed how to publish your own package using poetry
in this article.
*CI (Continuous Integration) - The practice of automatically building and testing code changes after they are merged into the repository*
*CD (Continuous Delivery) - The automated delivery of code to development or production environments*
GitHub supports Actions, which are automated processes that can run one or more jobs, including CI/CD
pipelines.
Workflows are what GitHub will run according to your configuration. They should be located in the .github/workflows
directory and be .yaml
files.
For example, if we want to run tests after each commit, we need to create a file named test.yml
.
I chose the name test
for convenience and to indicate that this particular workflow is responsible for tests, but it can be anything.
name: "Test"
on:
pull_request:
types:
- "opened"
- "synchronize"
- "reopened"
push:
branches:
- '*'
workflow_dispatch:
jobs:
test:
runs-on: ubuntu-latest
strategy:
fail-fast: false
steps:
- uses: actions/checkout@v4
- name: Set up Python 3.12
id: setup-python
uses: actions/setup-python@v5
with:
python-version: 3.12
- name: Cache poetry install
uses: actions/cache@v4
with:
path: ~/.local
key: poetry-${{ steps.setup-python.outputs.python-version }}-1.7.1-0
- uses: snok/install-poetry@v1
with:
version: 1.7.1
virtualenvs-create: true
virtualenvs-in-project: true
- name: Load cached venv
id: cached-poetry-dependencies
uses: actions/cache@v4
with:
path: .venv
key: venv-${{ steps.setup-python.outputs.python-version }}-${{ hashFiles('**/poetry.lock') }}
- name: Install dependencies
if: steps.cached-poetry-dependencies.outputs.cache-hit != 'true'
run: poetry install --no-interaction
- name: Run tests
run: poetry run pytest --cov-report=xml
name
- This is the name of the workflow.
pull request
- Specifies the types of pull requests that trigger the workflow.
The recommended values by default are opened
, synchronize
, and reopened
.
push
- Specifies the branches that trigger the workflow.
jobs
- A workflow can consist of one or more jobs, which run in parallel by default but can also be configured to run sequentially.
The runs-on
key specifies which operating system the jobs will run on.
Setting strategy: fail-fast: false
means that GitHub will not cancel all in-progress and queued jobs in the matrix
if any job in the matrix fails.
The matrix
parameter will be discussed later.
As you can see, there are a lot of similar codes in the uses
key in the steps. For example:
actions/setup-python@v5
actions/cache@v4
snok/install-poetry@v1
At first glance, the names of the actions might be confusing, but they are actually the paths to the GitHub repositories for these actions,
excluding the @v
part.
For example, you can view the source code of the first action by visiting //github.com/actions/setup-python
.
The same can be done for the other actions. @v
is used to set which version of the action is to use.
For example, if we want to install Python 3.12
, we pass python-version: 3.12
in the Set up Python 3.12
step. The same approach applies to other steps.
Python
with the required version.Poetry
1.7.1 dependency using GitHub’s cache.Python
version and poetry.lock
.
I will demonstrate how to include a service in a workflow, using PostgreSQL
as an example.
jobs:
test:
services:
postgres:
image: postgres:latest
env:
POSTGRES_PASSWORD: postgres
ports:
- 5432:5432
options: >-
--health-cmd pg_isready
--health-interval 10s
--health-timeout 5s
--health-retries 5
Adding a services
key with a required service name allows to set up it during a job.
Moreover, you can pass settings to it similarly when using docker
.
For example, you may need to run a job with several specific Python
versions, not just the latest, or with different dependencies.
This can be particularly useful when you need to support backward compatibility with older dependencies. GitHub
provides the strategy key to address this. The matrix
parameter allows you to run a job with multiple values.
jobs:
test:
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
python-version: ["3.11", "3.12"]
sqlalchemy-version: ["1.4.52", "2.0.31"]
As a result, there will be four runs of the job with the following combinations of Python
and SQLAlchemy
versions, respectively:
3.11
, 1.4.52
3.11
, 2.0.31
3.12
, 1.4.52
3.12
, 2.0.31
You can access the parameters in a job run using ${{ matrix.python-version }}
or ${{ matrix.sqlalchemy-version }}
.
- name: Set up Python ${{ matrix.python-version }}
id: setup-python
uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}
- name: Install sqlalchemy ${{ matrix.sqlalchemy-version }}
run: pip install sqlalchemy==${{ matrix.sqlalchemy-version }}
You can access it from the repository settings menu at //github.com/{Author}/{Repository}/settings/rules
.
After creating a new ruleset, scroll down to Require status checks to pass
, turn it on, and specify which jobs are required to pass.
name: Upload Python Package to PyPi on release
on:
release:
types: [published]
permissions:
contents: read
jobs:
deploy:
runs-on: ubuntu-latest
environment:
name: pypi
url: //pypi.org/project/dataclass-sqlalchemy-mixins/
steps:
- uses: actions/checkout@v4
- name: Set up Python
uses: actions/setup-python@v3
with:
python-version: '3.x'
- uses: snok/install-poetry@v1
with:
version: 1.7.1
virtualenvs-create: true
virtualenvs-in-project: true
- name: Build package
run: poetry build
- name: Publish package
uses: pypa/gh-action-pypi-publish@27b31702a0e7fc50959f5ad993c78deac1bdfc29
with:
user: __token__
password: ${{ secrets.PYPI_API_TOKEN }}
This workflow:
Python
.Poetry
1.7.1 dependency using GitHub’s cache.
You just need to add a PYPI_API_TOKEN
to repository secrets(//github.com/{Author}/{Repository}/settings/secrets/actions
).
Finally, create a new release (//github.com/{Author}/{Repository}/releases
), and the package will be uploaded.