Skip to content

CI/CD Reporters

Truthound provides reporters optimized for major CI/CD platforms.

CI reporters still start from the canonical ValidationRunResult contract used across Truthound 3.0. Internally, they build the shared RunPresentation and then format against a LegacyValidationResultView compatibility projection so provider-specific emitters can keep using flattened rows and summary helpers.

That split is intentional:

  • external caller input: ValidationRunResult
  • shared render projection: RunPresentation
  • CI formatting layer: LegacyValidationResultView

If you only need generic JSON, Markdown, or HTML artifacts, use the built-in validation reporters instead of the CI family.

Supported Platforms

Platform Reporter Key Features
GitHub Actions GitHubActionsReporter Annotations, Step Summaries, Output Variables
GitLab CI GitLabCIReporter Code Quality JSON, JUnit XML, Collapsible Sections
Jenkins JenkinsReporter JUnit XML, warnings-ng JSON
Azure DevOps AzureDevOpsReporter VSO Commands, Variables, Task Results
CircleCI CircleCIReporter Test Metadata, Artifacts
Bitbucket Pipelines BitbucketPipelinesReporter Reports, Annotations

Auto-Detection

Automatically detects the CI platform and creates the appropriate reporter:

from truthound.reporters.ci import get_ci_reporter, detect_ci_platform

# Auto-detect
platform = detect_ci_platform()
reporter = get_ci_reporter()  # Reporter for detected platform

# Explicit specification
reporter = get_ci_reporter("github")
reporter = get_ci_reporter("gitlab")
reporter = get_ci_reporter("jenkins")

Retrieve Environment Information

from truthound.reporters.ci import get_ci_environment

env = get_ci_environment()
print(f"Platform: {env.platform}")
print(f"Is PR: {env.is_pr}")
print(f"Branch: {env.branch}")
print(f"Commit: {env.commit}")
print(f"Build ID: {env.build_id}")
print(f"Build URL: {env.build_url}")

GitHub Actions

Basic Usage

from truthound.reporters.ci import GitHubActionsReporter

reporter = GitHubActionsReporter()
exit_code = reporter.report_to_ci(run_result)

Configuration Options

Option Type Default Description
step_summary bool True Write summary to GITHUB_STEP_SUMMARY file
use_groups bool True Use ::group:: commands
emoji_enabled bool True Include emojis
set_output bool False Set workflow output variables
output_name str "validation_result" Output variable name prefix

Workflow Example

# .github/workflows/validate.yml
name: Data Validation
on: [push, pull_request]

jobs:
  validate:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-python@v5
        with:
          python-version: '3.11'

      - name: Install dependencies
        run: pip install truthound

      - name: Run validation
        run: |
          python -c "
          import truthound as th
          from truthound.reporters.ci import GitHubActionsReporter

          result = th.check('data/*.csv')
          reporter = GitHubActionsReporter(set_output=True)
          exit_code = reporter.report_to_ci(result)
          exit(exit_code)
          "

      # Use output variables (when set_output=True)
      - name: Use output
        if: always()
        run: |
          echo "Success: ${{ steps.validate.outputs.validation_result_success }}"
          echo "Issues: ${{ steps.validate.outputs.validation_result_issues }}"

Output Format

Annotations (code comments):

::error file=data.csv,line=10,title=NullValidator::Found 5 null values (5 occurrences)
::warning file=data.csv,line=20,title=RangeValidator::3 values out of range

Step Summary (displayed in Job Summary):

## ✅ Truthound Validation Report

### Summary

| Metric | Value |
|--------|-------|
| **Status** | PASSED |
| **Data Asset** | `customer_data.csv` |
| **Total Validators** | 10 |
...


GitLab CI

Basic Usage

from truthound.reporters.ci import GitLabCIReporter

reporter = GitLabCIReporter(
    code_quality_path="gl-code-quality-report.json",
    output_format="both"  # code_quality + junit
)
exit_code = reporter.report_to_ci(run_result)

Configuration Options

Option Type Default Description
code_quality_path str "gl-code-quality-report.json" Code Quality report path
junit_path str "gl-junit-report.xml" JUnit report path
output_format str "code_quality" "code_quality", "junit", "both"
include_fingerprint bool True Include fingerprint for deduplication
collapse_sections bool True Use collapsible sections

.gitlab-ci.yml Example

validate:
  stage: test
  script:
    - pip install truthound
    - python -c "
        import truthound as th
        from truthound.reporters.ci import GitLabCIReporter

        result = th.check('data/')
        reporter = GitLabCIReporter(output_format='both')
        exit_code = reporter.report_to_ci(result)
        exit(exit_code)
      "
  artifacts:
    reports:
      codequality: gl-code-quality-report.json
      junit: gl-junit-report.xml
    when: always

Code Quality Report Format

[
  {
    "description": "Found 5 null values",
    "check_name": "NullValidator",
    "severity": "critical",
    "categories": ["Data Quality"],
    "location": {
      "path": "data.csv",
      "lines": { "begin": 1 }
    },
    "fingerprint": "abc123def456..."
  }
]

Jenkins

Basic Usage

from truthound.reporters.ci import JenkinsReporter

reporter = JenkinsReporter(
    junit_path="junit-report.xml",
    output_format="junit"
)
exit_code = reporter.report_to_ci(run_result)

Configuration Options

Option Type Default Description
junit_path str "junit-report.xml" JUnit XML path
warnings_path str "warnings-report.json" warnings-ng JSON path
output_format str "junit" "junit", "warnings", "both"
testsuite_name str "Truthound Validation" Test suite name
include_stdout bool True Include system-out
use_pipeline_steps bool True Use Pipeline step annotations

Jenkinsfile Example

pipeline {
    agent any

    stages {
        stage('Validate') {
            steps {
                sh '''
                    pip install truthound
                    python -c "
import truthound as th
from truthound.reporters.ci import JenkinsReporter

result = th.check('data/')
reporter = JenkinsReporter(output_format='junit')
exit_code = reporter.report_to_ci(result)
exit(exit_code)
"
                '''
            }
            post {
                always {
                    junit 'junit-report.xml'
                }
            }
        }
    }
}

JUnit XML Output

<?xml version="1.0" encoding="UTF-8"?>
<testsuites name="Truthound Validation Results" tests="10" failures="2" errors="0">
  <testsuite name="Truthound Validation" tests="10" failures="2" timestamp="2024-01-15T10:30:45">
    <properties>
      <property name="data_asset" value="customer_data.csv"/>
      <property name="run_id" value="abc123"/>
    </properties>
    <testcase classname="truthound.email" name="NullValidator" time="0.001">
      <failure type="null_values" message="Found 5 null values">
        Validator: NullValidator
        Severity: critical
        Issue Count: 5
      </failure>
    </testcase>
  </testsuite>
</testsuites>

Azure DevOps

Basic Usage

from truthound.reporters.ci import AzureDevOpsReporter

reporter = AzureDevOpsReporter(
    variable_prefix="TRUTHOUND",
    upload_summary=True
)
exit_code = reporter.report_to_ci(run_result)

Configuration Options

Option Type Default Description
set_variable bool True Set pipeline variables
variable_prefix str "TRUTHOUND" Variable name prefix
upload_summary bool True Upload markdown summary
summary_path str "truthound-summary.md" Summary file path
use_task_commands bool True Use task.complete commands
timeline_records bool False Create timeline records

azure-pipelines.yml Example

trigger:
  - main

pool:
  vmImage: 'ubuntu-latest'

steps:
  - task: UsePythonVersion@0
    inputs:
      versionSpec: '3.11'

  - script: |
      pip install truthound
      python -c "
      import truthound as th
      from truthound.reporters.ci import AzureDevOpsReporter

      result = th.check('data/')
      reporter = AzureDevOpsReporter()
      exit_code = reporter.report_to_ci(result)
      exit(exit_code)
      "
    displayName: 'Run validation'

  # Use set variables
  - script: |
      echo "Success: $(TRUTHOUND_SUCCESS)"
      echo "Total Issues: $(TRUTHOUND_TOTAL_ISSUES)"
    condition: always()
    displayName: 'Check results'

VSO Command Output

##vso[task.logissue type=error;sourcepath=data.csv;linenumber=10;code=NullValidator]Found 5 null values
##vso[task.setvariable variable=TRUTHOUND_SUCCESS;isOutput=true;]false
##vso[task.setvariable variable=TRUTHOUND_TOTAL_ISSUES;isOutput=true;]5
##vso[task.uploadsummary]truthound-summary.md
##vso[task.complete result=Failed;]Validation failed with critical issues

CircleCI

Basic Usage

from truthound.reporters.ci import CircleCIReporter

reporter = CircleCIReporter()
exit_code = reporter.report_to_ci(run_result)

config.yml Example

version: 2.1

jobs:
  validate:
    docker:
      - image: cimg/python:3.11
    steps:
      - checkout
      - run:
          name: Install dependencies
          command: pip install truthound
      - run:
          name: Run validation
          command: |
            python -c "
            import truthound as th
            from truthound.reporters.ci import CircleCIReporter

            result = th.check('data/')
            reporter = CircleCIReporter()
            exit_code = reporter.report_to_ci(result)
            exit(exit_code)
            "
      - store_test_results:
          path: test-results

Bitbucket Pipelines

Basic Usage

from truthound.reporters.ci import BitbucketPipelinesReporter

reporter = BitbucketPipelinesReporter()
exit_code = reporter.report_to_ci(run_result)

bitbucket-pipelines.yml Example

pipelines:
  default:
    - step:
        name: Validate data
        script:
          - pip install truthound
          - python -c "
              import truthound as th
              from truthound.reporters.ci import BitbucketPipelinesReporter

              result = th.check('data/')
              reporter = BitbucketPipelinesReporter()
              exit_code = reporter.report_to_ci(result)
              exit(exit_code)
            "

Common Configuration (CIReporterConfig)

All CI reporters share CIReporterConfig-based settings:

Option Type Default Description
fail_on_error bool True Non-zero exit code on error
fail_on_warning bool False Non-zero exit code on warning
annotations_enabled bool True Generate code annotations
summary_enabled bool True Generate summary report
max_annotations int 50 Maximum annotation count
group_by_file bool True Group annotations by file
include_passed bool False Include passed items
artifact_path str \| None None Artifact path
custom_properties dict {} Platform-specific custom properties

Annotation System

AnnotationLevel

Converts validation severity to CI platform annotation levels:

from truthound.reporters.ci import AnnotationLevel

# Severity → Level conversion
level = AnnotationLevel.from_severity("critical")  # ERROR
level = AnnotationLevel.from_severity("high")      # ERROR
level = AnnotationLevel.from_severity("medium")    # WARNING
level = AnnotationLevel.from_severity("low")       # NOTICE
Severity Annotation Level
critical, high ERROR
medium WARNING
low NOTICE
Other INFO

CIAnnotation

Platform-independent annotation representation:

from truthound.reporters.ci import CIAnnotation, AnnotationLevel

annotation = CIAnnotation(
    message="Found 5 null values",
    level=AnnotationLevel.ERROR,
    file="data.csv",
    line=10,
    column=5,
    title="NullValidator",
    validator_name="NullValidator",
    raw_severity="critical"
)

Custom CI Reporter Registration

from truthound.reporters.ci import BaseCIReporter, register_ci_reporter, CIPlatform

@register_ci_reporter("my_ci")
class MyCIReporter(BaseCIReporter):
    platform = CIPlatform.GENERIC
    name = "my_ci"

    def format_annotation(self, annotation):
        return f"[{annotation.level.value}] {annotation.message}"

    def format_summary(self, result):
        return f"Validation: {result.status.value}"

# Usage
reporter = get_ci_reporter("my_ci")

API Reference

BaseCIReporter

class BaseCIReporter(ValidationReporter[CIReporterConfig]):
    """Base class for CI reporters."""

    platform: CIPlatform = CIPlatform.GENERIC
    supports_annotations: bool = True
    supports_summary: bool = True
    max_annotations_limit: int = 50

    @abstractmethod
    def format_annotation(self, annotation: CIAnnotation) -> str:
        """Platform-specific annotation format."""
        ...

    @abstractmethod
    def format_summary(self, result: LegacyValidationResultView) -> str:
        """Platform-specific summary format."""
        ...

    def report_to_ci(self, result: Any) -> int:
        """Output to CI and return exit code."""
        ...

    def get_exit_code(self, result: LegacyValidationResultView) -> int:
        """Determine exit code based on result."""
        ...