Skip to content

CI/CD Reporters

Truthound provides reporters optimized for major CI/CD platforms.

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(validation_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(validation_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(validation_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(validation_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(validation_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(validation_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: ValidationResult) -> str:
        """Platform-specific summary format."""
        ...

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

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