Skip to main content

A PySpark-based Data Quality Framework using YAML-configurable checks.

Project description

๐Ÿ›ก๏ธ PySpark Data Quality Framework (pyspark_dq_framework)

This framework provides a config-driven, extensible Data Quality (DQ) framework built on top of PySpark.
It allows defining validation checks in a config.yml file and running them on one or more DataFrames.


๐Ÿ“ Project Structure

pyspark_dq_framework/
โ”œโ”€โ”€ pyspark_dq/                   # your Python package
โ”‚   โ”œโ”€โ”€ __init__.py
โ”‚   โ”œโ”€โ”€ dq_checks.py              # Core data quality functions
โ”‚   โ”œโ”€โ”€ config.py                 # YAML parser to config DataFrame
โ”‚   โ”œโ”€โ”€ run_dq.py                 # Engine to run checks with logging
โ”‚   โ””โ”€โ”€ validate_checks.py        # Check validation
โ”œโ”€โ”€ README.md
โ”œโ”€โ”€ LICENSE
โ”œโ”€โ”€ MANIFEST.in
โ”œโ”€โ”€ setup.py

โš™๏ธ Configuration (config.yml)

return_mode: summary   # options: summary (default) | summary+clean | summary+error | all

model:
  - name: df
    columns:
      - name: ID
        checks:
          - null_check
      - name: Age
        checks:
          - null_check
          - positive_age_check:
              min_age: 21

  - name: df2
    columns:
      - name: ID
        checks:
          - null_check
      - name: Salary
        checks:
          - non_negative_check

๐Ÿ› ๏ธ Code Example for Executing framework

If config file is in :

  • local: pass file path in config_path parameter
  • S3: pass s3 path
  • Azure Blob Storage or (ADLS Gen2 for data lake): pass blob storage path
  • Google Cloud Storage (GCS): pass GCS path

Code Example:

from pyspark.sql import SparkSession
from pyspark.sql.functions import col, lit
from pyspark_dq.run_dq import run_dq_pipeline
from pyspark_dq.dq_checks import *

# Custom check
def positive_age_check(df, row, min_age=0):
    table = row["table"]
    column = row["column"]
    check_type = "positive_age_check"

    valid_df = df.filter(col(column) >= min_age)
    invalid_df = df.filter(col(column) < min_age) \
                   .withColumn("table", lit(table)) \
                   .withColumn("column", lit(column)) \
                   .withColumn("check_type", lit(check_type))

    log_df = df.sparkSession.createDataFrame(
        [(table, column, check_type, invalid_df.count() == 0, invalid_df.count())],
        ["table", "column", "check_type", "passed", "invalid_count"]
    )
    return valid_df, invalid_df, log_df

if __name__ == "__main__":
    
    spark = SparkSession.builder.appName("pyspark_dq_framework").getOrCreate()

    # Example Data
    data1 = [(1, "Ram", 20), (None, "Shyam", 20)]
    df = spark.createDataFrame(data1, ["ID", "Name", "Age"])

    data2 = [
        (1, "Ram", "Software Engineer", 20000),
        (2, "Shyam", "Product Manager", 80000),
        (3, "Radha", "Senior Software Engineer", 50000)
    ]
    df2 = spark.createDataFrame(data2, ["ID", "Name", "Designation", "Salary"])

    df_dict = {"df": df, "df2": df2}

    # Register checks
    check_function_map = {
        "null_check": null_check,
        "unique_check": unique_check,
        "allowed_values_check": allowed_values_check,
        "range_check": range_check,
        "non_negative_check": non_negative_check,
        "regex_check": regex_check,
        "not_empty_check": not_empty_check,
        "data_type_check": data_type_check,
        "positive_age_check": positive_age_check
    }

    # Run pipeline
    results = run_dq_pipeline(
        spark,
        df_dict=df_dict,
        config_path="config.yml",
        checks=check_function_map
    )

    print(results)

๐Ÿ”„ Return Modes

The return_mode in config.yml controls what is returned:

  • summary โ†’ Only summary logs
  • summary+clean โ†’ Summary + cleaned DataFrames
  • summary+error โ†’ Summary + error DataFrames
  • all โ†’ Summary + clean + error DataFrames

โš™๏ธ Prerequisites

Before installing and using the pyspark-dq-framework package, ensure the following Python modules are available:

Module Version Description
pyspark >=3.5.5 Required for all Spark DataFrame operations. If running on Databricks, EMR, or other managed Spark clusters, PySpark is usually pre-installed.
pyyaml >=6.0.2 Required to read YAML configuration files for defining dynamic data quality checks.

๐Ÿ’ก Tip: PySpark can be installed optionally with the package using:

pip install pyspark-dq-framework[pyspark]

If you already have PySpark installed on your cluster, you can just install:

pip install pyspark-dq-framework

โ–ถ๏ธ How to Run

๐Ÿ“ฆ Install PyPi package in your environment

pip install pyspark-dq-framework

๐Ÿ“š Import required libraries

from pyspark.sql import SparkSession
from pyspark_dq.dq_checks import *
from pyspark_dq.run_dq import run_dq_pipeline

โšก run_dq_pipeline Function

Runs the Data Quality (DQ) pipeline on one or more Spark DataFrames according to a YAML configuration and user-defined checks.

๐Ÿ“ Parameters

Parameter Type Description
spark SparkSession The active Spark session to execute the DQ pipeline. All Spark DataFrame operations will be performed using this session.
df_dict Dict[str, DataFrame] A dictionary of input DataFrames to validate. Keys are table names (strings), and values are the corresponding Spark DataFrames. The pipeline will process each DataFrame according to its configuration.
config_path str Path to the YAML configuration file that defines the DQ checks for each table and column. This configuration specifies which checks to run, expected types, regex patterns, or other rules.
checks Dict[str, callable], optional A dictionary mapping check names (strings) to Python functions. Each function implements a DQ check, takes a DataFrame and a configuration row, and returns (valid_df, invalid_df, log_df). This allows adding custom checks dynamically.
max_json_records int, optional, default=100 Maximum number of failing rows per check to include in the summary JSON column. If set to None, all failed rows will be captured. This is useful for controlling summary size while maintaining accurate failed counts.

๐ŸŽฏ Returns

A dictionary with the following structure:

{
    "table_name": {
        "clean": DataFrame,  # final cleaned DataFrame after all checks
        "error": DataFrame   # combined invalid rows across all checks (optional, depending on return mode)
    },
    "summary": DataFrame      # summary table with failed counts and JSON of failed rows
}

๐Ÿš€ Example Usage

from pyspark.sql import SparkSession

spark = SparkSession.builder.getOrCreate()

# Dictionary of input DataFrames
df_dict = {
    "sales": sales_df,
    "customers": customers_df
}

# Dictionary of custom DQ check functions
checks = {
    "not_empty_check": not_empty_check,
    "data_type_check": data_type_check
}

results = run_dq_pipeline(
    spark,
    df_dict=df_dict,
    config_path="config.yaml",
    checks=checks,
    max_json_records=100
)

summary_df = results["summary"]
clean_sales_df = results["sales"]["clean"]
error_sales_df = results["sales"]["error"]

clean_customers_df = results["customers"]["clean"]
error_customers_df = results["customers"]["error"]

๐Ÿ’ป Add sample code without custom check (here I am taking file name as main.py)

/main.py

from pyspark.sql import SparkSession
from pyspark_dq.run_dq import run_dq_pipeline
from pyspark_dq.dq_checks import null_check, unique_check
    
spark = SparkSession.builder.appName("pyspark_dq_framework").getOrCreate()

# Example Data
# Dataframe 1
data1 = [
          (1, "Ram", 20),
          (None, "Shyam", 20)
        ]
df1 = spark.createDataFrame(data1, ["ID", "Name", "Age"])

# Datframe 2
data2 = [
          (1, "Ram", "Software Engineer", 20000),
          (2, "Shyam", "Product Manager", 80000),
          (3, "Radha", "Senior Software Engineer", 50000)
        ]
df2 = spark.createDataFrame(data2, ["ID", "Name", "Designation", "Salary"])

# Dataframes Dictionary
df_dict = {"df1": df1, "df2": df2}

# Register checks
check_function_map = {
        "null_check": null_check,
        "unique_check": unique_check
    }

# Run pipeline
results = run_dq_pipeline(
            spark,
            df_dict=df_dict,
            config_path="config.yml",
            checks=check_function_map
          )

print(results)

โšก Add sample code with custom check (here I am taking file name as main.py)

/main.py

from pyspark.sql import SparkSession
from pyspark_dq.run_dq import run_dq_pipeline
from pyspark_dq.dq_checks import null_check, unique_check


# Custom check
def positive_age_check(df, row, min_age=0):
    table = row["table"]
    column = row["column"]
    check_type = "positive_age_check"

    valid_df = df.filter(col(column) >= min_age)
    invalid_df = df.filter(col(column) < min_age) \
                   .withColumn("table", lit(table)) \
                   .withColumn("column", lit(column)) \
                   .withColumn("check_type", lit(check_type))

    log_df = df.sparkSession.createDataFrame(
        [(table, column, check_type, invalid_df.count() == 0, invalid_df.count())],
        ["table", "column", "check_type", "passed", "invalid_count"]
    )
    return valid_df, invalid_df, log_df

# Inititalize spark session 
spark = SparkSession.builder.appName("pyspark_dq_framework").getOrCreate()

# Example Data
# Dataframe 1
data1 = [
          (1, "Ram", 20),
          (None, "Shyam", 20)
        ]
df1 = spark.createDataFrame(data1, ["ID", "Name", "Age"])

# Datframe 2
data2 = [
          (1, "Ram", "Software Engineer", 20000),
          (2, "Shyam", "Product Manager", 80000),
          (3, "Radha", "Senior Software Engineer", 50000)
        ]
df2 = spark.createDataFrame(data2, ["ID", "Name", "Designation", "Salary"])

# Dataframes Dictionary
df_dict = {"df1": df1, "df2": df2}

# Register checks
check_function_map = {
        "null_check": null_check,
        "unique_check": unique_check,
        "positive_age_check": positive_age_check
    }

# Run pipeline
results = run_dq_pipeline(
            spark,
            df_dict=df_dict,
            config_path="config.yml",
            checks=check_function_map
          )

print(results)

๐Ÿ“ General Config YAML Structure

/config.yml

return_mode: <return mode>   # options: summary (default) | summary+clean | summary+error | all

model:
  - name: <dataframe_name>
    columns:
      - name: <column_name>
        checks:
          - <check_name>
          - <check_name_with_params>:
              param1: value
              param2: value

๐Ÿ’ป Run locally

python main.py

๐Ÿ“Š Run in Databricks

dbutils.notebook.run("main", 60)

โ˜๏ธ Run with S3 config

python main.py --config s3://my-bucket/config.yml

โœ… Features

  • Config-driven DQ checks
  • Works with local, S3, GCP, Azure, DBFS
  • Extensible with custom checks
  • Flexible return_mode for different use cases

๐Ÿ“‹ Supported Data Quality Checks

  • null_check
  • unique_check
  • allowed_values_check
  • range_check
  • non_negative_check
  • regex_check
  • not_empty_check
  • data_type_check

Check Name Description Parameters
null_check Ensures no null values. None
unique_check Ensures uniqueness of values. None
allowed_values_check Ensures value is in allowed set. allowed_values: [list_of_values]
range_check Ensures value is within numeric range. min_value, max_value (optional)
non_negative_check Ensures value >= 0. None
regex_check Ensures value matches regex pattern. pattern: "<regex>"
not_empty_check Ensures dataset/column is not empty. None
data_type_check Ensures value matches expected data type. expected_type
positive_age_check Ensures age is >= min_age. min_age

๐Ÿ“‘ YAML Usage Examples for Each Check

Check Name YAML Example
null_check - null_check
unique_check - unique_check
allowed_values_check - allowed_values_check:\n allowed_values: [Male, Female, Other]
range_check - range_check:\n min_value: 10\n max_value: 100
non_negative_check - non_negative_check
regex_check - regex_check:\n pattern: "^[A-Za-z0-9_]+$"
not_empty_check - not_empty_check
data_type_check - data_type_check:\n expected_type: IntegerType
positive_age_check - positive_age_check:\n min_age: 21

๐Ÿ“‘ Explaination of each check with proper YAML format for config.yml file

null_check

  • Ensures that the column does not contain NULL values.
  • Records with NULL values will go into the invalid dataset.
model:
  - name: df
    columns:
      - name: ID
        checks:
          - null_check

unique_check

  • Ensures that the column contains unique values.
  • Duplicates will be flagged as invalid.
model:
  - name: df
    columns:
      - name: ID
        checks:
          - unique_check

allowed_values_check

  • Ensures that the column values belong to a defined set of allowed values.
  • Invalid values are flagged.

Parameters

  • allowed_values โ†’ list of allowed values.
model:
  - name: df
    columns:
      - name: Status
        checks:
          - allowed_values_check:
              allowed_values: ["Active", "Inactive", "Pending"]

range_check

  • Ensures that the column values fall within a numeric range.

Parameters

  • min_value โ†’ minimum acceptable value (optional).
  • max_value โ†’ maximum acceptable value (optional).
model:
  - name: df
    columns:
      - name: Age
        checks:
          - range_check:
              min_value: 18
              max_value: 60

non_negative_check

  • Ensures that the column values are greater than or equal to 0.
model:
  - name: df
    columns:
      - name: Salary
        checks:
          - non_negative_check

regex_check

  • Ensures that the column values match a given regex pattern.
  • Useful for emails, phone numbers, codes, etc.

Parameters

  • pattern โ†’ valid regex pattern.
model:
  - name: df
    columns:
      - name: Email
        checks:
          - regex_check:
              pattern: "^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\\.[A-Z|a-z]{2,}$"

not_empty_check

  • Ensures that the column (or dataset) is not empty.
  • If no records exist, the check fails.
model:
  - name: df
    columns:
      - name: ID
        checks:
          - not_empty_check

data_type_check

  • Ensures that the column matches the expected data type.
  • You need to provide the expected type in parameters

Parameters

  • expected_type โ†’ e.g., IntegerType, StringType, DoubleType.
model:
  - name: df
    columns:
      - name: Age
        checks:
          - data_type_check:
              expected_type: IntegerType

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

pyspark_dq_framework-1.0.0.tar.gz (14.2 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

pyspark_dq_framework-1.0.0-py3-none-any.whl (11.1 kB view details)

Uploaded Python 3

File details

Details for the file pyspark_dq_framework-1.0.0.tar.gz.

File metadata

  • Download URL: pyspark_dq_framework-1.0.0.tar.gz
  • Upload date:
  • Size: 14.2 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.1.0 CPython/3.13.3

File hashes

Hashes for pyspark_dq_framework-1.0.0.tar.gz
Algorithm Hash digest
SHA256 ff85369ba3bff1e2a607361b346b8ab14f27a30b18adffcd820509929b15a382
MD5 608ce24040ecf9477fb492752aa29fcc
BLAKE2b-256 3d7604f200f1b08666ccf6fcc3400a5fb7936a34d14df3d617f25435878778ff

See more details on using hashes here.

File details

Details for the file pyspark_dq_framework-1.0.0-py3-none-any.whl.

File metadata

File hashes

Hashes for pyspark_dq_framework-1.0.0-py3-none-any.whl
Algorithm Hash digest
SHA256 495846e72997fdd3b151e9bea76ec0f93a3f35595a0612e5ab5e3455e4691703
MD5 46d3eef68e42e575f84d1a00aafd60ce
BLAKE2b-256 923d16433cb43685b768085ce16f8243e475aeacdf7e1ee6c2cfde5f90fbb9a2

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page