Skip to content

GitHub Actions

No double quotes

Warning

Never use double quotes inside ${{ ... } as it is simply not supported.

Tertiary

GHA has a really funky tertiary: ${{ x && 'ifTrue' || 'ifFalse' }}

This only works if <ifTrue> isn't the empty string. If <ifTrue> == '' then '' is considered as false, which then evaluates the right hand side of the ||.

There are lots of gotchas here, and this is a great thread highlighting more of it.

Example

steps:
- name: stuff
  env:
  PR_NUMBER_OR_MASTER: ${{ github.event.number == 0 && 'master' ||  format('pr-{0}', github.event.number)  }}

Expressions

Expressions official docs here.

Functions

This stores a value in an environment variable. Using the contains function, the if-condition evaluates whether to run the step or not.

steps:
  - run: echo "POETRY_VERSION=$(poetry --version)" >> $GITHUB_ENV
    shell: bash

  - run: poetry config installer.modern-installation false
    # workaround for bug: https://github.com/microsoft/debugpy/issues/1246
    if: ${{ contains(env.POETRY_VERSION, '1.4.1') }}
    shell: bash

Python specific

Pipx via actions/setup-python

When using actions/setup-python and Poetry, you can use pipx to install Poetry outside of your project's virtual environment. The setup-python action provides pipx by default, but you might notice how it is not running under the Python version you chose.

To fix this, you can pass the --python argument to pipx.

Example

steps:
- uses: actions/setup-python@v4
  id: cpython_setup
  with:
    python-version: "3.11"

- run: pipx install <package> --python '${{ steps.cpython_setup.outputs.python-path }}'

Python package caching

The actions/setup-python action has built in caching, which you should likely use unless you have specific needs like support for dependency groups. For the latter use case, you can look to actions/cache.

actions/cache

When using actions/cache, you can use the ${{ steps.<python setup step id>.outputs.python-version }} as part of the cache key:

steps:
- uses: actions/setup-python@v4
  id: cpython_setup
  with:
    python-version: "3.11"

- uses: actions/cache@v3
  id: python-cache
  env:
    SEGMENT_DOWNLOAD_TIMEOUT_MIN: "15"
  with:
    path: |
      ~/.cache/pip
      ~/.cache/pypoetry
    key: pip-poetry-${{ runner.os }}-${{ runner.arch }}-py-${{ steps.cpython_setup.outputs.python-version }}-${{ hashFiles('poetry.lock') }}

The cache key can be extended with e.g. Poetry's dependency groups.

Python shell

Python code can be written inline of a step, with the shell: python directive.

Example

steps:
  - name: Display the path
    run: |
      import os
      print(os.environ['PATH'])
    shell: python

See the jobs.<job_id>.steps[*].shell docs for additional shells that are supported.

Test coverage comment in PR

Here's one way to add a PR commit about the changed files' test coverage, powered by romeovs/lcov-reporter-action.

Warning

Unfortunately, it will generate a GitHub notification for each comment posted.

Example

# .github/workflows/pr_cov_comment.yml

steps:
  - name: run tests wrapped by coverage
    run: coverage run -m pytest

  - name: export coverage to lcov format
    run: coverage lcov

  - uses: romeovs/lcov-reporter-action@2a28ec3e25fb7eae9cb537e9141603486f810d1a
    # The reason for using a hash rather than a version/tag, is the project
    # failed in publishing this: https://github.com/romeovs/lcov-reporter-action/issues/47
    with:
      lcov-file: ./coverage/coverage.lcov
      filter-changed-files: true
      delete-old-comments: true

  - name: export coverage to lcov format
    run: coverage html

  - name: Archive code coverage results
    uses: actions/upload-artifact@v3
    with:
      name: code-coverage-report
      path: coverage/html

Configure pytest and coverage in pyproject.toml:

[tool.coverage.run]
omit = ["tests/migrations/*"]
source = ["src/mypkg"]

[tool.coverage.report]
exclude_lines =[
  "pragma: nocover",
  "if TYPE_CHECKING",
  "@overload",
]
skip_covered = true

[tool.coverage.html]
directory = "coverage/html"

[tool.coverage.lcov]
output = "coverage/coverage.lcov"

[tool.pytest.ini_options]
testpaths = "tests"
addopts = "-rxXs --color=yes"