[Trunk Check Guide: Run Linters in Python Venv]
Important! This solution may not be a temporary implementation, since the Trunk team has a plan in the roadmap of their project to improve this aspect, but now it is a simple and working solution.
In this write-up, we will explore how to run Trunk linters not in an isolated environment but within a pre-created Python virtual environment using Poetry. This approach leverages the benefits of Poetry's dependency management while ensuring that the linters are executed in a controlled and consistent environment.
Pre-configuration
Installation
curl https://get.trunk.io -fsSL | bash
➡️ Additional Trunk installation documentation
Trunk setup
1. Initialize trunk in your project
trunk init
2. Modify your .trunk/trunk.yaml
version: 0.1
lint:
enabled:
# Code - Python
- bandit
- black
- flake8
- isort
- mypy
- ruff
In this case, we do not explicitly specify the linter versions, since they will be taken frompyproject.toml
. However, when doingtrunk install
ortrunk upgrade
Trunk will prompt you to upgrade the provided linters, if possible.
3. Create virtual environment for defining dependencies for linters
◻️ You can create .venv
at the root of project by setting virtualenvs.in-project
configuration parameter:
⠀⠀◽️By running command: poetry config virtualenvs.in-project true
⠀⠀◽️By editing configuration manually
[virtualenvs]
in-project = true
◻️ You can create a virtual environment in the default poetry cache directory
poetry install
4. Add lint: definitions: <python_linter>: environment
block in your .trunk/trunk.yaml
configuration:
version: 0.1
lint:
definitions:
- name: bandit
environment:
- name: PATH
list: [/absolute/path/to/project/.venv/bin]
commands:
- run: bandit --exit-zero -c pyproject.toml --format json --output ${tmpfile} ${target}
direct_configs: [pyproject.toml]
- name: black
environment:
- name: PATH
list: [/absolute/path/to/project/.venv/bin]
- name: flake8
environment:
- name: PATH
list: [/absolute/path/to/project/.venv/bin]
- name: isort
environment:
- name: PATH
list: [/absolute/path/to/project/.venv/bin]
- name: mypy
environment:
- name: PATH
list: [/absolute/path/to/project/.venv/bin]
- name: ruff
environment:
- name: PATH
list: [/absolute/path/to/project/.venv/bin]
- name: LD_LIBRARY_PATH
value: /home/runner/_work/_tool/Python/3.12.2/x64/lib
Notes:
◻️ This is necessary to run python linters from .venv
manage the versions of these liners from pyproject.toml
◻️ In our case, when using Trunk Check in CI using the GitHub action setup-python
, we needed to hard-pin the path to the Python libraries:
runtime_environment:
- name: LD_LIBRARY_PATH
value: /home/runner/_work/_tool/Python/<PYTHON_VERSION>/x64/lib
◻️ You must specify the absolute path to .venv/bin
of the project depending on how you create the virtual environment:
⠀⠀◽️If you create .venv
at the root of project: /absolute/path/to/project/.venv/bin
⠀⠀◽️If you create .venv
in the default poetry cache directory: ~/.cache/pypoetry/virtualenvs/<project-hash-python-version>/bin
⠀⠀◽️You can do this in user.yaml
if you are going to use trunk in your CI/CD.
◻️ Additionally, the bandit
linter in Trunk by default cannot work with the configuration from pyproject.toml
, so we explicitly specify the config file in the bandit commands
:
commands:
- run: bandit --exit-zero -c pyproject.toml --format json --output ${tmpfile} ${target}
direct_configs: [pyproject.toml]
5. (Optional) Disable trunk telemetry
Set the environment variable:
TRUNK_TELEMETRY=off
Usage
◻️ Run linters & formatters on your changed files, prompting you to apply fixes. Without additional args, trunk check
will run all applicable linters on all files changed in the current branch
trunk check -a
◻️ Run all applicable formatters as configured in trunk.yaml. trunk fmt
is short-hand for running trunk check
with a --fix
--filter
set to all formatters enabled in your repository
trunk fmt -a
CI
The advantage of using Trunk as a linter aggregator in CI is that by default it can scan only changed files and uses caching in CI.
GitHib Actions Configuration
Prepare your pipelines to work with Trunk. In our case we will use the official GitHub Trunk action.
Trunk based CI contains 2 components:
1. Main workflow – .github/workflows/trunk-check-pr.yaml
Description: The main workflow that runs linters and formatters on changed files.
name: Trunk Check PR
on: [pull_request]
concurrency:
group: ${{ github.head_ref || github.run_id }}
cancel-in-progress: true
permissions: read-all
jobs:
trunk_check:
name: Trunk Check
runs-on: ubuntu-latest
timeout-minutes: 10
permissions:
checks: write
contents: read
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Trunk Check
uses: trunk-io/trunk-action@v1
Notes:
You can logically divide your linters into groups by adding block with: arguments: --filter=<your,linters,comma,separated>
:
with:
arguments: --filter=cspell,prettier
E.G.:
name: Trunk Check PR
...
jobs:
trunk_check_common:
name: Common linters
...
- name: Trunk Check Common
uses: trunk-io/trunk-action@v1
with:
arguments: --filter=<COMMON_LINTERS>
trunk_check_formatting:
name: Formatting linters
...
- name: Trunk Check Formatting
uses: trunk-io/trunk-action@v1
with:
arguments: --filter=<FORMATTING_LINTERS>
trunk_check_code:
name: Code linters
...
- name: Trunk Check Code
uses: trunk-io/trunk-action@v1
with:
arguments: --filter=<CODE_LINTERS>
trunk_check_security:
name: Security linters
...
- name: Trunk Check Security
uses: trunk-io/trunk-action@v1
with:
arguments: --filter=<SECURITY_LINTERS>
2. Trunk setup-ci configuration – .trunk/setup-ci/action.yaml
Description: The composite workflow that runs before each trunk check
job. In our case, it is used to prepare a virtual environment with linters dependencies.
name: Poetry venv setup
description: Set up dependencies for Trunk Check
runs:
using: composite
steps:
- name: Set up Python environment
uses: actions/setup-python@v5
with:
python-version: <PYTHON_VERSION>
id: setup-python
# trunk-ignore-all(prettier)
- name: Load cached venv
uses: actions/cache@v3
with:
path: .venv
key: venv-${{ runner.os }}-${{ steps.setup-python.outputs.python-version }}-${{ hashFiles('**/poetry.lock') }}
id: cache-poetry-dependencies
- name: Install dependencies and setup a local virtual environment
if: steps.cache-poetry-dependencies.outputs.cache-hit != 'true'
shell: bash
run: |
pip install poetry==<POETRY_VERSION>
poetry config virtualenvs.in-project true
poetry install --no-interaction
Conclusion
By following these steps, we have successfully configured Trunk to run linters within a Python virtual environment. This approach ensures that your linting tools are executed in a consistent and controlled environment, and, importantly, all linter dependencies in such a configuration are managed from one place – pyproject.toml
and are easily cached, both locally and within CI. It was also convenient for us, as it also allows us to use all the same linters outside of Trunk for those who do not want to use this toolkit.