Downloads

Use Data Quality Gates Locally in Python

Use this page when you want to validate records in your own Python code without sending them over the network for evaluation.

Here, local means the firewall definition runs inside your own Python runtime, wherever that runs — your machine, a job on a data platform, or a service in your pipeline — not only your local computer. Unlike the DQ Firewalls API, where you send records to Ataccama ONE for evaluation, DQ Gates let you run the firewall definition yourself in your own runtime, so no data leaves your environment.

Before you begin

Make sure you’ve reviewed prerequisites and limitations before continuing.

The exported firewall definition ZIP file contains the firewall definition together with other assets needed for local evaluation, such as reference data and metadata files. Make sure the ZIP file is accessible to the Python runtime at evaluation time.

How it works

The Python flow has four steps:

  1. Install the ONE Python SDK with Python DQ Engine.

  2. Authenticate to Ataccama ONE.

  3. Download and export the firewall definition to a ZIP file.

  4. Evaluate records locally in Python.

Installation

Install the ONE Python SDK and Python DQ Engine packages:

pip install ataccama_one-<version>-py3-none-any.whl ataccama_one_expressions-<version>-py3-none-any.whl

Configuration

Create an authenticated client

Create a client using the credentials from Authentication setup:

import os

from dotenv import find_dotenv, load_dotenv

from ataccama_one import Client, OpenIdConnectAuth


def get_env_var(name: str) -> str:
    value = os.getenv(name)
    if value is None:
        raise ValueError(f"Environment variable {name} is required but not set.")
    return value


load_dotenv(find_dotenv(".env"))

client = Client(
    platform_version=get_env_var("ATACCAMA_PLATFORM_VERSION"),
    url=get_env_var("ATACCAMA_INSTANCE_URL"),
    auth=OpenIdConnectAuth(
        client_id=get_env_var("ATACCAMA_CLIENT_ID"),
        client_secret=get_env_var("ATACCAMA_CLIENT_SECRET"),
        keycloak_host=get_env_var("ATACCAMA_KEYCLOAK_HOST"),
        keycloak_realm=get_env_var("ATACCAMA_KEYCLOAK_REALM"),
    ),
)

If you run into SSL or certificate issues while connecting to Ataccama ONE, see Data Quality Gates Troubleshooting.

Fetch and export the firewall definition

Local Python execution works with exported DQ firewall definitions.

firewall_id = get_env_var("YOUR_DQ_FIREWALL_ID")
client.export_firewall_definition(firewall_id, save_to="firewall_definition.zip")

If you want to fetch multiple firewalls at once, use client.get_firewall_definitions(), which returns a Python iterator:

# Adjust `firewall_filter` to limit which firewalls are fetched from Ataccama ONE
firewall_filter = None

for firewall in client.get_firewall_definitions(filter=firewall_filter):
    client.export_firewall_definition(
        firewall.firewall_id,
        save_to=f"{firewall.name}.zip",
    )

For details about filtering fetched firewalls, see Firewall filtering.

Evaluate data locally

Load the exported firewall definition ZIP file into the local Python runtime. The ZIP contains the firewall definition together with other assets needed for local evaluation, such as reference data and metadata files.

After the ZIP is loaded, validate_records(…​) evaluates the input records locally in Python without sending them to Ataccama ONE. validate_records(…​) takes a Python list, where each item is one record, and each record is a list of attribute values in the order configured on the firewall.

The following example loads a firewall and evaluates a small list of records written inline:

Evaluating records inline
from ataccama_one.local import DqFirewall

firewall = DqFirewall.from_file("continent_firewall_definition.zip")
results = firewall.validate_records([
    ["invalid"],
    ["africa"],
    ["america"],
    ["prague"],
    ["south america"],
    ["europe"],
])

print(results.record_results)

In practice, your records usually come from a table or file rather than being written inline. validate_records(…​) requires a plain Python list, where each record is a list of attribute values, so prepare that list however suits your environment. The following example reads the records from a CSV file using the standard library. Because csv.reader also returns the header row, skip it before evaluation.

Reading records with the standard library
import csv

# Load your source data into a list of records, where each record is a
# list of attribute values. Here, the values are read from a CSV file.
with open("customers.csv", newline="") as file:
    reader = csv.reader(file)
    next(reader)  # skip the header row
    records = list(reader)

results = firewall.validate_records(records)

If you already work with pandas, you can read the data into a DataFrame instead. pandas skips the header automatically and can optimize the column data types.

Reading records with pandas
import pandas as pd

# na_filter=False keeps empty values as empty strings instead of NaN.
input_frame = pd.read_csv("customers.csv", na_filter=False)
records = input_frame.values.tolist()

results = firewall.validate_records(records)
The attribute values in each record must match the number, order, and data types of the input attributes configured on the firewall in Ataccama ONE.

You get one result record back for every input record. The results are returned as a separate object and are not merged into your input data automatically (see Integrate DQ Gates into your data pipelines).

Assign record identifiers

Each result has a record_id. By default, this is a generated value in the form record-0, record-1, and so on, based on the record’s position. Results are always returned in the same order as the input records, so you can pair each result with its source record by index without any extra configuration.

If you’d prefer a meaningful identifier, such as a primary key or customer number, pass a record_identifier function to validate_records(…​). This function receives each record and its index and returns the string to use as that record’s record_id.

The following example uses a key column from the source data as the identifier:

Assigning a record identifier
# Return the value to use as each record's identifier.
# Adapt this to your data: here, the first value in each record is used.
def get_record_id(record, index):
    return str(records[index][0])

results = firewall.validate_records(records, record_identifier=get_record_id)

This is only an example. Decide what uniquely identifies a record in your data, such as a primary key or customer number, and implement the function to return it.

Results

validate_records(…​) returns a DqFirewallResult object. Its record_results attribute is a list with one entry per evaluated record.

Record result

Each entry in record_results is a DqFirewallRecordResult with the following attributes:

Attribute Description

record_id

Identifier of the record. Set this with the record_identifier parameter (see Assign record identifiers); otherwise, a default value is used.

overall_result

Overall DQ result for the record: PASSED or FAILED.

score

Combined data quality score for the record, calculated from all of its rule instance results.

rule_instance_results

List of per-rule results for the record (see Rule instance result).

A record’s overall_result is PASSED only if it passes all DQ rules applied to it. The exception is rules belonging to a DQ dimension that has Overall contribution set to false. Results of such rules don’t count towards the overall result, so even if they fail, the record can still be PASSED.

Rule instance result

Each entry in rule_instance_results is a DqFirewallRuleInstanceResult with the following attributes:

Attribute Description

rule_instance_label

Label of the rule instance, as defined when the rule was applied to the attribute in the firewall.

overall_result

Result of this rule instance: PASSED or FAILED.

dimension_result

Result for the DQ dimension the rule belongs to, for example, VALID, INVALID, or COMPLETE. The possible values depend on the rule’s dimension.

explanation

Explanation for the result, as defined in the rule implementation, for example, INVALID_EMAIL_FORMAT or WRONG_REGIONAL_PREFIX. OTHER is used as a fallback when no specific explanation applies.

The result holds the same information as the DQ firewall API results, but uses Python naming: attribute names use underscores, and each rule result is identified by rule_instance_label rather than a rule instance ID.

Sample result object (output trimmed)
DqFirewallResult(
    record_results=[
        DqFirewallRecordResult(
            record_id='893794994',
            overall_result='FAILED',
            score=1000,
            rule_instance_results=[
                DqFirewallRuleInstanceResult(
                    rule_instance_label='Not empty (String)',
                    overall_result='PASSED',
                    dimension_result='COMPLETE',
                    explanation='OTHER',
                ),
                DqFirewallRuleInstanceResult(
                    rule_instance_label='customername evaluation',
                    overall_result='FAILED',
                    dimension_result='INVALID',
                    explanation='INVALID_FORMAT',
                ),
                DqFirewallRuleInstanceResult(
                    rule_instance_label='accuracy prefix',
                    overall_result='FAILED',
                    dimension_result='INVALID',
                    explanation='WRONG_REGIONAL_PREFIX',
                ),
            ],
        ),
    ],
)

Access the results in Python

Iterate over record_results and read the attributes you need. Because the results are Python objects, you access values as attributes, for example, record_result.overall_result:

Reading the results
for record_result in results.record_results:
    print(record_result.record_id, record_result.overall_result, record_result.score)

    for rule_result in record_result.rule_instance_results:
        print(" ", rule_result.rule_instance_label, rule_result.overall_result)

If you use pandas, you can flatten the results into a DataFrame for a more readable, tabular view.

Viewing the results as a pandas DataFrame
import pandas as pd

flat_data = []


def mark_failed(input: str) -> str:
    return f"🔴 {input}" if "FAILED" in input else f"🟢 {input}"


for record_result in results.record_results:
    flat_data.append(
        {
            "record_id": record_result.record_id,
            "overall_result": mark_failed(record_result.overall_result),
            "score": record_result.score,
            **{
                f"{rule.rule_instance_label}": f"{mark_failed(rule.overall_result)}  "
                f"(dimension: {rule.dimension_result} - {rule.explanation})"
                for rule in record_result.rule_instance_results
            },
        }
    )

results_frame = pd.DataFrame(flat_data)
results_frame

Integrate DQ Gates into your data pipelines

The following examples are illustrative. The ONE Python SDK evaluates records and returns results; it does not read your input data or write your output.

You decide how to read your source data and where to send valid, failed, or quarantined records, and you implement that logic in a way that suits your use case.

Combine results with the original records

Pair each source record with its DQ result in a single list, so you keep the original values and the outcome together.

Combining input records with their results
combined = []
for source_record, record_result in zip(records, results.record_results):
    combined.append({
        "record": source_record,
        "record_id": record_result.record_id,
        "overall_result": record_result.overall_result,
        "score": record_result.score,
        "rule_instance_results": record_result.rule_instance_results,
    })

If you used the pandas approach for reading and viewing the records, combining the input with the results is a single call.

Combining input records with their results using pandas
combined_frame = pd.concat([input_frame, results_frame], axis=1)

Filter valid records and quarantine failures

Route records based on their result: pass valid records to the next layer, and send failed records to a quarantine target together with the explanations of why they failed.

Filtering valid records and quarantining failures
valid_records = []
quarantined_records = []

# Choose a score threshold that fits your quality requirements.
SCORE_THRESHOLD = 1000

for source_record, record_result in zip(records, results.record_results):
    if record_result.overall_result == "PASSED":
        valid_records.append(source_record)
    elif record_result.score < SCORE_THRESHOLD:
        failed_reasons = [
            (rule.rule_instance_label, rule.explanation)
            for rule in record_result.rule_instance_results
            if rule.overall_result == "FAILED"
        ]
        quarantined_records.append((source_record, record_result.score, failed_reasons))

# Pass valid_records on to the next layer.
# Write quarantined_records, with their explanations, to your quarantine target.

Write only results or only failures

Persist just the DQ results, or filter to only the records that failed, depending on what you need downstream.

Writing only the results, or only the failures
# Only the DQ results, without the source data.
results_only = [
    {
        "record_id": record_result.record_id,
        "overall_result": record_result.overall_result,
        "score": record_result.score,
    }
    for record_result in results.record_results
]

# Only the records that failed, from the combined list.
failed_only = [row for row in combined if row["overall_result"] == "FAILED"]

Gate a pipeline on data quality

Calculate the pass rate for a batch and stop the pipeline if it falls below a threshold. You can also record the pass rate over time for monitoring and reporting.

Gating a pipeline on the batch pass rate
total = len(results.record_results)
passed = sum(1 for r in results.record_results if r.overall_result == "PASSED")
pass_rate = (passed / total) * 100 if total else 0

# Set the minimum acceptable pass rate for your pipeline.
PASS_RATE_THRESHOLD = 95
if pass_rate < PASS_RATE_THRESHOLD:
    raise RuntimeError(f"Data quality below threshold: {pass_rate:.1f}% passed")

# For monitoring, record pass_rate and the passed/total counts to your metrics store.

Keep firewall definitions up to date

When you change a firewall’s configuration in Ataccama ONE, for example, by adding or editing rules, re-export the firewall definition so that local evaluation uses the latest version. To refresh the ZIP file, re-run the export step (see Fetch and export the firewall definition).

Was this page useful?