Installing Dependencies
Your workflow can print a Python version, but it can’t run your tests yet — pytest isn’t installed on the runner by default, and neither is anything else your project needs. This chapter adds the dependency-installation step properly, instead of just bolting on a pip install and moving on.
What CI Pipeline Project Actually Needs
Take stock of the project first. send_file.py and receive_file.py only use Python’s standard library — sockets, no third-party networking package required. The one thing you need that isn’t built in is pytest, to run the test suite in tests/.
That’s a short dependency list, which makes it a good project to learn the pattern on before you hit a real requirements.txt with a dozen packages and version constraints.
Why pip install --upgrade pip Comes First
You’ll see this line at the top of almost every CI dependency step you look at:
python -m pip install --upgrade pipThe runner’s baked-in pip version can lag behind. An older pip is more likely to resolve dependencies incorrectly, miss a newer wheel format, or just behave differently than the pip on your machine. Upgrading it first costs a few seconds and removes a whole category of “works locally, fails in CI” surprises.
Note
Run this as python -m pip install ..., not bare pip install .... Calling pip through python -m guarantees you’re upgrading the pip tied to the Python interpreter setup-python just installed — not some other pip that happens to be first on the runner’s PATH.
Writing A requirements.txt
Even for a single dependency, put it in a file rather than typing it inline in the workflow. It gives you one place to check what a project needs, and it’s what makes the caching in the next section possible at all.
At the root of the project:
pytestThat’s the whole file. Leaving the version unpinned means CI always installs whatever the latest pytest release is. That’s fine for a learning project, but worth knowing the trade-off: an unpinned dependency can change out from under you between one CI run and the next if a new major version ships with breaking changes. Pinning (pytest==8.4.0, for example) trades that risk for having to bump the version yourself when you want updates. Either is a legitimate choice — just make it on purpose.
Installing From requirements.txt In CI
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install -r requirements.txtTwo commands, one step — pip is current, then your actual dependency goes in.
A First Look At Caching
Re-downloading pytest from PyPI on every single push works, but it’s wasted time once your dependency list grows. setup-python has a built-in shortcut for the common case: point it at your dependency file and it’ll cache the downloaded packages between runs.
- name: Setup Python
uses: actions/setup-python@v7
with:
python-version: "3.13"
cache: "pip"
cache-dependency-path: requirements.txtWith this in place, a run where requirements.txt hasn’t changed reuses the cached packages instead of hitting PyPI again. This is a convenience wrapper around a more general caching mechanism in GitHub Actions — you’ll get the full picture of how that mechanism actually works, and when you’d reach for it directly instead of this shortcut, later in this section.
Hands-On: Wiring It Into Your Workflow
Add the requirements.txt file:
cat > requirements.txt << 'EOF'
pytest
EOFUpdate .github/workflows/ci.yml so the Setup Python step gains the cache inputs, and a new Install dependencies step follows it:
name: CI
on:
push:
branches: [main]
pull_request:
branches: [main]
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v7
- name: Setup Python
uses: actions/setup-python@v7
with:
python-version: "3.13"
cache: "pip"
cache-dependency-path: requirements.txt
- name: Confirm Python Version
run: python --version
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install -r requirements.txtCommit and push:
git add requirements.txt .github/workflows/ci.yml
git commit -m "Install pytest from requirements.txt, enable pip caching"
git pushCheck the Actions tab. The first run will show a cache miss (nothing to reuse yet) — push again with no changes (git commit --allow-empty -m "Trigger action") to requirements.txt and the second run’s Setup Python step will report a cache hit which significantly fasten installation when requirements grow bigger. pytest is now sitting on the runner, ready to actually be called — which is exactly what the next two chapters do.