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'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.