[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 from pyproject.toml. However, when doing trunk install or trunk upgrade Trunk will prompt you to upgrade the provided linters, if possible.

➡️ Additional Trunk linters documentation

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

➡️ Additional Trunk usage documentation


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

➡️ Additional Trunk CI setup


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.