Python projects can have significant dependency install times, especially with packages such as numpy, pandas or tensorflow that require compilation. The cache brings that time down from several minutes to a few seconds.
What you will learn
- Enable the pip cache built into
setup-python - Cache Poetry and Pipenv depending on your dependency manager
- Configure
actions/cachefor fine-grained control of the paths - Cache a complete virtual environment to skip the install altogether
- Avoid the traps: a cache invalidated by a Python upgrade, a double cache
The built-in cache of setup-python
The simplest method uses the cache built into actions/setup-python:
- uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0 with: python-version: '3.11' cache: 'pip' # Enables the pip cache
- run: pip install -r requirements.txtWith Poetry
If you manage your dependencies with Poetry, tell setup-python about it:
the cache will then key off poetry.lock.
- uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0 with: python-version: '3.11' cache: 'poetry'
- run: | pip install poetry poetry installWith Pipenv
The same principle for Pipenv: the cache then follows Pipfile.lock.
- uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0 with: python-version: '3.11' cache: 'pipenv'
- run: | pip install pipenv pipenv install --devManual caching with actions/cache
For more control, use actions/cache directly:
- uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0 with: python-version: '3.11'
- uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 with: path: ~/.cache/pip key: pip-${{ runner.os }}-${{ hashFiles('**/requirements*.txt') }} restore-keys: | pip-${{ runner.os }}-
- run: pip install -r requirements.txtThe cache paths per manager
If you configure actions/cache by hand, the path: depends on the manager
and on the operating system. This table gives the locations to cache.
| Manager | Linux path | macOS path | Windows path |
|---|---|---|---|
| pip | ~/.cache/pip | ~/Library/Caches/pip | ~\AppData\Local\pip\Cache |
| Poetry | ~/.cache/pypoetry | ~/Library/Caches/pypoetry | %APPDATA%\pypoetry\Cache |
| Pipenv | ~/.cache/pipenv | ~/Library/Caches/pipenv | %USERPROFILE%\.pipenv |
Caching the virtual environment
For maximum gains, cache the complete virtual environment:
- uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0 with: python-version: '3.11'
- name: Cache virtualenv uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 id: cache-venv with: path: .venv key: venv-${{ runner.os }}-${{ hashFiles('**/requirements*.txt') }}
- name: Create venv and install deps if: steps.cache-venv.outputs.cache-hit != 'true' run: | python -m venv .venv source .venv/bin/activate pip install -r requirements.txt
- name: Run tests run: | source .venv/bin/activate pytestGain: the install is skipped entirely when the cache exists.
A complete Python workflow
name: Python CI
on: [push, pull_request]
# No rights by default: the job asks for the minimumpermissions: {}
jobs: test: runs-on: ubuntu-24.04 permissions: contents: read strategy: matrix: python-version: ['3.10', '3.11', '3.12']
steps: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: persist-credentials: false
- name: Setup Python ${{ matrix.python-version }} uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0 with: python-version: ${{ matrix.python-version }} cache: 'pip' cache-dependency-path: | requirements.txt requirements-dev.txt
- name: Install dependencies run: | pip install --upgrade pip pip install -r requirements.txt pip install -r requirements-dev.txt
- name: Run linters run: | ruff check . mypy .
- name: Run tests run: pytest --cov=src --cov-report=xml
- name: Upload coverage uses: codecov/codecov-action@fb8b3582c8e4def4969c97caa2f19720cb33a72f # v7.0.0 with: files: ./coverage.xmlCaching with Poetry (advanced)
name: Python CI with Poetry
on: [push, pull_request]
permissions: {}
jobs: test: runs-on: ubuntu-24.04 permissions: contents: read steps: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: persist-credentials: false
- name: Setup Python uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0 with: python-version: '3.11'
- name: Install Poetry uses: snok/install-poetry@a783c322200f0519c7926aa6faa857c4e23e9263 # v1.4.2 with: version: '1.8.2' virtualenvs-create: true virtualenvs-in-project: true
- name: Cache Poetry virtualenv uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 with: path: .venv key: poetry-${{ runner.os }}-${{ hashFiles('**/poetry.lock') }} restore-keys: | poetry-${{ runner.os }}-
- name: Install dependencies run: poetry install --no-interaction
- name: Run tests run: poetry run pytestThe specific optimisations
Packages with native dependencies
For numpy, scipy and pandas, which carry compiled dependencies:
- name: Cache pip wheels uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 with: path: | ~/.cache/pip ~/.local/lib/python*/site-packages key: pip-wheels-${{ runner.os }}-${{ hashFiles('**/requirements*.txt') }}Pre-commit hooks
Pre-commit environments are slow to rebuild. Caching them makes the hook almost instant from the second run onwards.
- name: Cache pre-commit uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 with: path: ~/.cache/pre-commit key: pre-commit-${{ runner.os }}-${{ hashFiles('.pre-commit-config.yaml') }}
- run: pre-commit run --all-filesThe common mistakes
A cache invalidated by a Python upgrade
A pip cache depends on the Python version. If the key does not include it, an upgrade restores packages compiled for the previous one, and everything breaks.
# ❌ The key does not change when Python changeskey: pip-${{ hashFiles('requirements.txt') }}
# ✅ Include the Python versionkey: pip-${{ runner.os }}-py${{ matrix.python-version }}-${{ hashFiles('requirements.txt') }}A conflict with the setup-python cache
Stacking the cache: of setup-python and an actions/cache on the same
path produces two managers stepping on each other. Keep only one.
# ❌ A double cache, with unpredictable behaviour- uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0 with: cache: 'pip'- uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 # v4.2.3 with: path: ~/.cache/pip
# ✅ Use one or the other- uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0 with: cache: 'pip'Check your understanding
Make sure the essentials of this guide are yours. The questions only cover what was explained here.
Knowledge check
Check what you have learned with this interactive quiz
How it works
- The timer starts when you click Start
- Multiple choice, true/false and short answer questions
- You can move between questions
- Detailed results are shown at the end
Starts the quiz and the timer
Review
(0/0)Skills profile
What to do next
Resources to go further
Hints before you try again?
A new full quiz with random questions
Work again only on the questions you missed
Key points
- The built-in cache of
setup-python(cache: 'pip') is enough for most projects, in one line. - For Poetry or Pipenv, name the right manager:
cache: 'poetry',cache: 'pipenv'. actions/cachegives you control over the paths: useful for compiled wheels or pre-commit hooks.- Caching the complete virtualenv skips the install outright, the maximum gain.
- Include the Python version in the cache key, otherwise a Python upgrade restores an incompatible cache.
- Never stack the
cache:ofsetup-pythonand anactions/cacheon the same path: pick one.
Next steps
- Java cache: the same reasoning about keys and paths applied to Maven and Gradle, useful as soon as a repository mixes several languages.
- Concurrency: the complementary lever, which avoids paying for several Python installs on commits that are already obsolete.
- Introduction to runners: what explains why the machine starts blank on every run, and therefore why the pip cache exists.