Building and testing Python
Learn how to create a continuous integration (CI) workflow to build and test your Python project.
In this article
Introduction
This guide shows you how to build, test, and publish a Python package.
GitHub-hosted runners have a tools cache with pre-installed software, which includes Python and PyPy. You don't have to install anything! For a full list of up-to-date software and the pre-installed versions of Python and PyPy, seeGitHub-hosted runners.
Prerequisites
You should be familiar with YAML and the syntax for GitHub Actions. For more information, seeWriting workflows.
We recommend that you have a basic understanding of Python, and pip. For more information, see:
Using a Python workflow template
To get started quickly, add a workflow template to the.github/workflows
directory of your repository.
GitHub provides a workflow template for Python that should work if your repository already contains at least one.py
file. The subsequent sections of this guide give examples of how you can customize this workflow template.
On GitHub, navigate to the main page of the repository.
Under your repository name, click Actions.
If you already have a workflow in your repository, clickNew workflow.
The "Choose a workflow" page shows a selection of recommended workflow templates. Search for "Python application".
On the "Python application" workflow, clickConfigure.
Edit the workflow as required. For example, change the Python version.
ClickCommit changes.
The
python-app.yml
workflow file is added to the.github/workflows
directory of your repository.
Specifying a Python version
To use a pre-installed version of Python or PyPy on a GitHub-hosted runner, use thesetup-python
action. This action finds a specific version of Python or PyPy from the tools cache on each runner and adds the necessary binaries toPATH
, which persists for the rest of the job. If a specific version of Python is not pre-installed in the tools cache, thesetup-python
action will download and set up the appropriate version from thepython-versions
repository.
Using thesetup-python
action is the recommended way of using Python with GitHub Actions because it ensures consistent behavior across different runners and different versions of Python. If you are using a self-hosted runner, you must install Python and add it toPATH
. For more information, see thesetup-python
action.
The table below describes the locations for the tools cache in each GitHub-hosted runner.
Ubuntu | Mac | Windows | |
---|---|---|---|
Tool Cache Directory | /opt/hostedtoolcache/* | /Users/runner/hostedtoolcache/* | C:\hostedtoolcache\windows\* |
Python Tool Cache | /opt/hostedtoolcache/Python/* | /Users/runner/hostedtoolcache/Python/* | C:\hostedtoolcache\windows\Python\* |
PyPy Tool Cache | /opt/hostedtoolcache/PyPy/* | /Users/runner/hostedtoolcache/PyPy/* | C:\hostedtoolcache\windows\PyPy\* |
If you are using a self-hosted runner, you can configure the runner to use thesetup-python
action to manage your dependencies. For more information, seeusing setup-python with a self-hosted runner in thesetup-python
README.
GitHub supports semantic versioning syntax. For more information, seeUsing semantic versioning and theSemantic versioning specification.
Using multiple Python versions
The following example uses a matrix for the job to set up multiple Python versions. For more information, seeRunning variations of jobs in a workflow.
name: Python packageon: [push]jobs: build: runs-on: ubuntu-latest strategy: matrix: python-version: ["pypy3.10", "3.9", "3.10", "3.11", "3.12", "3.13"] steps: - uses: actions/checkout@v5 - name: Set up Python ${{ matrix.python-version }} uses: actions/setup-python@v5 with: python-version: ${{ matrix.python-version }} # You can test your matrix by printing the current Python version - name: Display Python version run: python -c "import sys; print(sys.version)"
name:Pythonpackageon: [push]jobs:build:runs-on:ubuntu-lateststrategy:matrix:python-version: ["pypy3.10","3.9","3.10","3.11","3.12","3.13"]steps:-uses:actions/checkout@v5-name:SetupPython${{matrix.python-version}}uses:actions/setup-python@v5with:python-version:${{matrix.python-version}}# You can test your matrix by printing the current Python version-name:DisplayPythonversionrun:python-c"import sys; print(sys.version)"
Using a specific Python version
You can configure a specific version of Python. For example, 3.12. Alternatively, you can use semantic version syntax to get the latest minor release. This example uses the latest minor release of Python 3.
name: Python packageon: [push]jobs: build: runs-on: ubuntu-latest steps: - uses: actions/checkout@v5 - name: Set up Python # This is the version of the action for setting up Python, not the Python version. uses: actions/setup-python@v5 with: # Semantic version range syntax or exact version of a Python version python-version: '3.x' # Optional - x64 or x86 architecture, defaults to x64 architecture: 'x64' # You can test your matrix by printing the current Python version - name: Display Python version run: python -c "import sys; print(sys.version)"
name:Pythonpackageon: [push]jobs:build:runs-on:ubuntu-lateststeps:-uses:actions/checkout@v5-name:SetupPython# This is the version of the action for setting up Python, not the Python version.uses:actions/setup-python@v5with:# Semantic version range syntax or exact version of a Python versionpython-version:'3.x'# Optional - x64 or x86 architecture, defaults to x64architecture:'x64'# You can test your matrix by printing the current Python version-name:DisplayPythonversionrun:python-c"import sys; print(sys.version)"
Excluding a version
If you specify a version of Python that is not available,setup-python
fails with an error such as:##[error]Version 3.7 with arch x64 not found
. The error message includes the available versions.
You can also use theexclude
keyword in your workflow if there is a configuration of Python that you do not wish to run. For more information, seeWorkflow syntax for GitHub Actions.
name: Python packageon: [push]jobs: build: runs-on: ${{ matrix.os }} strategy: matrix: os: [ubuntu-latest, macos-latest, windows-latest] python-version: ["3.9", "3.11", "3.13", "pypy3.10"] exclude: - os: macos-latest python-version: "3.11" - os: windows-latest python-version: "3.11"
name:Pythonpackageon: [push]jobs:build:runs-on:${{matrix.os}}strategy:matrix:os: [ubuntu-latest,macos-latest,windows-latest]python-version: ["3.9","3.11","3.13","pypy3.10"]exclude:-os:macos-latestpython-version:"3.11"-os:windows-latestpython-version:"3.11"
Using the default Python version
We recommend usingsetup-python
to configure the version of Python used in your workflows because it helps make your dependencies explicit. If you don't usesetup-python
, the default version of Python set inPATH
is used in any shell when you callpython
. The default version of Python varies between GitHub-hosted runners, which may cause unexpected changes or use an older version than expected.
GitHub-hosted runner | Description |
---|---|
Ubuntu | Ubuntu runners have multiple versions of system Python installed under/usr/bin/python and/usr/bin/python3 . The Python versions that come packaged with Ubuntu are in addition to the versions that GitHub installs in the tools cache. |
Windows | Excluding the versions of Python that are in the tools cache, Windows does not ship with an equivalent version of system Python. To maintain consistent behavior with other runners and to allow Python to be used out-of-the-box without thesetup-python action, GitHub adds a few versions from the tools cache toPATH . |
macOS | The macOS runners have more than one version of system Python installed, in addition to the versions that are part of the tools cache. The system Python versions are located in the/usr/local/Cellar/python/* directory. |
Installing dependencies
GitHub-hosted runners have the pip package manager installed. You can use pip to install dependencies from the PyPI package registry before building and testing your code. For example, the YAML below installs or upgrades thepip
package installer and thesetuptools
andwheel
packages.
You can also cache dependencies to speed up your workflow. For more information, seeDependency caching reference.
steps:- uses: actions/checkout@v5- name: Set up Python uses: actions/setup-python@v5 with: python-version: '3.x'- name: Install dependencies run: python -m pip install --upgrade pip setuptools wheel
steps:-uses:actions/checkout@v5-name:SetupPythonuses:actions/setup-python@v5with:python-version:'3.x'-name:Installdependenciesrun:python-mpipinstall--upgradepipsetuptoolswheel
Requirements file
After you updatepip
, a typical next step is to install dependencies fromrequirements.txt
. For more information, seepip.
steps:- uses: actions/checkout@v5- name: Set up Python uses: actions/setup-python@v5 with: python-version: '3.x'- name: Install dependencies run: | python -m pip install --upgrade pip pip install -r requirements.txt
steps:-uses:actions/checkout@v5-name:SetupPythonuses:actions/setup-python@v5with:python-version:'3.x'-name:Installdependenciesrun:| python -m pip install --upgrade pip pip install -r requirements.txt
Caching Dependencies
You can cache and restore the dependencies using thesetup-python
action.
The following example caches dependencies for pip.
steps:- uses: actions/checkout@v5- uses: actions/setup-python@v5 with: python-version: '3.12' cache: 'pip'- run: pip install -r requirements.txt- run: pip test
steps:-uses:actions/checkout@v5-uses:actions/setup-python@v5with:python-version:'3.12'cache:'pip'-run:pipinstall-rrequirements.txt-run:piptest
By default, thesetup-python
action searches for the dependency file (requirements.txt
for pip,Pipfile.lock
for pipenv orpoetry.lock
for poetry) in the whole repository. For more information, seeCaching packages dependencies in thesetup-python
README.
If you have a custom requirement or need finer controls for caching, you can use thecache
action. Pip caches dependencies in different locations, depending on the operating system of the runner. The path you'll need to cache may differ from the Ubuntu example above, depending on the operating system you use. For more information, seePython caching examples in thecache
action repository.
Testing your code
You can use the same commands that you use locally to build and test your code.
Testing with pytest and pytest-cov
This example installs or upgradespytest
andpytest-cov
. Tests are then run and output in JUnit format while code coverage results are output in Cobertura. For more information, seeJUnit andCobertura.
steps:- uses: actions/checkout@v5- name: Set up Python uses: actions/setup-python@v5 with: python-version: '3.x'- name: Install dependencies run: | python -m pip install --upgrade pip pip install -r requirements.txt- name: Test with pytest run: | pip install pytest pytest-cov pytest tests.py --doctest-modules --junitxml=junit/test-results.xml --cov=com --cov-report=xml --cov-report=html
steps:-uses:actions/checkout@v5-name:SetupPythonuses:actions/setup-python@v5with:python-version:'3.x'-name:Installdependenciesrun:| python -m pip install --upgrade pip pip install -r requirements.txt-name:Testwithpytestrun:| pip install pytest pytest-cov pytest tests.py --doctest-modules --junitxml=junit/test-results.xml --cov=com --cov-report=xml --cov-report=html
Using Ruff to lint and/or format code
The following example installs or upgradesruff
and uses it to lint all files. For more information, seeRuff.
steps:- uses: actions/checkout@v5- name: Set up Python uses: actions/setup-python@v5 with: python-version: '3.x'- name: Install the code linting and formatting tool Ruff run: pipx install ruff- name: Lint code with Ruff run: ruff check --output-format=github --target-version=py39- name: Check code formatting with Ruff run: ruff format --diff --target-version=py39 continue-on-error: true
steps:-uses:actions/checkout@v5-name:SetupPythonuses:actions/setup-python@v5with:python-version:'3.x'-name:InstallthecodelintingandformattingtoolRuffrun:pipxinstallruff-name:LintcodewithRuffrun:ruffcheck--output-format=github--target-version=py39-name:CheckcodeformattingwithRuffrun:ruffformat--diff--target-version=py39continue-on-error:true
The formatting step hascontinue-on-error: true
set. This will keep the workflow from failing if the formatting step doesn't succeed. Once you've addressed all of the formatting errors, you can remove this option so the workflow will catch new issues.
Running tests with tox
With GitHub Actions, you can run tests with tox and spread the work across multiple jobs. You'll need to invoke tox using the-e py
option to choose the version of Python in yourPATH
, rather than specifying a specific version. For more information, seetox.
name: Python packageon: [push]jobs: build: runs-on: ubuntu-latest strategy: matrix: python: ["3.9", "3.11", "3.13"] steps: - uses: actions/checkout@v5 - name: Setup Python uses: actions/setup-python@v5 with: python-version: ${{ matrix.python }} - name: Install tox and any other packages run: pip install tox - name: Run tox # Run tox using the version of Python in `PATH` run: tox -e py
name:Pythonpackageon: [push]jobs:build:runs-on:ubuntu-lateststrategy:matrix:python: ["3.9","3.11","3.13"]steps:-uses:actions/checkout@v5-name:SetupPythonuses:actions/setup-python@v5with:python-version:${{matrix.python}}-name:Installtoxandanyotherpackagesrun:pipinstalltox-name:Runtox# Run tox using the version of Python in `PATH`run:tox-epy
Packaging workflow data as artifacts
You can upload artifacts to view after a workflow completes. For example, you may need to save log files, core dumps, test results, or screenshots. For more information, seeStore and share data with workflow artifacts.
The following example demonstrates how you can use theupload-artifact
action to archive test results from runningpytest
. For more information, see theupload-artifact
action.
name: Python packageon: [push]jobs: build: runs-on: ubuntu-latest strategy: matrix: python-version: ["3.9", "3.10", "3.11", "3.12", "3.13"] steps: - uses: actions/checkout@v5 - name: Setup Python # Set Python version uses: actions/setup-python@v5 with: python-version: ${{ matrix.python-version }} # Install pip and pytest - name: Install dependencies run: | python -m pip install --upgrade pip pip install pytest - name: Test with pytest run: pytest tests.py --doctest-modules --junitxml=junit/test-results-${{ matrix.python-version }}.xml - name: Upload pytest test results uses: actions/upload-artifact@v4 with: name: pytest-results-${{ matrix.python-version }} path: junit/test-results-${{ matrix.python-version }}.xml # Use always() to always run this step to publish test results when there are test failures if: ${{ always() }}
name:Pythonpackageon: [push]jobs:build:runs-on:ubuntu-lateststrategy:matrix:python-version: ["3.9","3.10","3.11","3.12","3.13"]steps:-uses:actions/checkout@v5-name:SetupPython# Set Python versionuses:actions/setup-python@v5with:python-version:${{matrix.python-version}}# Install pip and pytest-name:Installdependenciesrun:| python -m pip install --upgrade pip pip install pytest-name:Testwithpytestrun:pytesttests.py--doctest-modules--junitxml=junit/test-results-${{matrix.python-version}}.xml-name:Uploadpytesttestresultsuses:actions/upload-artifact@v4with:name:pytest-results-${{matrix.python-version}}path:junit/test-results-${{matrix.python-version}}.xml# Use always() to always run this step to publish test results when there are test failuresif:${{always()}}
Publishing to PyPI
You can configure your workflow to publish your Python package to PyPI once your CI tests pass. This section demonstrates how you can use GitHub Actions to upload your package to PyPI each time you publish a release. For more information, seeManaging releases in a repository.
The example workflow below usesTrusted Publishing to authenticate with PyPI, eliminating the need for a manually configured API token.
# This workflow uses actions that are not certified by GitHub.# They are provided by a third-party and are governed by# separate terms of service, privacy policy, and support# documentation.# GitHub recommends pinning actions to a commit SHA.# To get a newer version, you will need to update the SHA.# You can also reference a tag or branch, but the action may change without warning.name: Upload Python Packageon: release: types: [published]permissions: contents: readjobs: release-build: runs-on: ubuntu-latest steps: - uses: actions/checkout@v5 - uses: actions/setup-python@v5 with: python-version: "3.x" - name: Build release distributions run: | # NOTE: put your own distribution build steps here. python -m pip install build python -m build - name: Upload distributions uses: actions/upload-artifact@v4 with: name: release-dists path: dist/ pypi-publish: runs-on: ubuntu-latest needs: - release-build permissions: # IMPORTANT: this permission is mandatory for trusted publishing id-token: write # Dedicated environments with protections for publishing are strongly recommended. environment: name: pypi # OPTIONAL: uncomment and update to include your PyPI project URL in the deployment status: # url: https://pypi.org/p/YOURPROJECT steps: - name: Retrieve release distributions uses: actions/download-artifact@v5 with: name: release-dists path: dist/ - name: Publish release distributions to PyPI uses: pypa/gh-action-pypi-publish@6f7e8d9c0b1a2c3d4e5f6a7b8c9d0e1f2a3b4c5d
# This workflow uses actions that are not certified by GitHub.# They are provided by a third-party and are governed by# separate terms of service, privacy policy, and support# documentation.# GitHub recommends pinning actions to a commit SHA.# To get a newer version, you will need to update the SHA.# You can also reference a tag or branch, but the action may change without warning.name:UploadPythonPackageon:release:types: [published]permissions:contents:readjobs:release-build:runs-on:ubuntu-lateststeps:-uses:actions/checkout@v5-uses:actions/setup-python@v5with:python-version:"3.x"-name:Buildreleasedistributionsrun:| # NOTE: put your own distribution build steps here. python -m pip install build python -m build-name:Uploaddistributionsuses:actions/upload-artifact@v4with:name:release-distspath:dist/pypi-publish:runs-on:ubuntu-latestneeds:-release-buildpermissions:# IMPORTANT: this permission is mandatory for trusted publishingid-token:write# Dedicated environments with protections for publishing are strongly recommended.environment:name:pypi# OPTIONAL: uncomment and update to include your PyPI project URL in the deployment status:# url: https://pypi.org/p/YOURPROJECTsteps:-name:Retrievereleasedistributionsuses:actions/download-artifact@v5with:name:release-distspath:dist/-name:PublishreleasedistributionstoPyPIuses:pypa/gh-action-pypi-publish@6f7e8d9c0b1a2c3d4e5f6a7b8c9d0e1f2a3b4c5d
For more information about this workflow, including the PyPI settingsneeded, seeConfiguring OpenID Connect in PyPI.