Contents

Chapter 1

PySpark Utility Library

PySpark Utils Library

Battle-tested utility functions for PySpark data engineering — transformations, data quality, SCD, schema evolution, logging, dedup, and DataFrame diffing.

Stop rewriting the same PySpark boilerplate on every project. This library gives you the production-ready building blocks that data engineering teams use daily — fully typed, tested, and documented.



What's Inside

ModuleWhat It Does
transformations15 reusable DataFrame transforms: column cleaning, casting, flattening, pivoting, hashing
data_qualityChainable DQ validation framework with structured reports and severity levels
scdSCD Type 1 (overwrite) and Type 2 (full history) merge utilities for Delta Lake
schema_utilsSchema comparison, evolution, DDL conversion, and compatibility checking
logging_utilsStructured pipeline logging with correlation IDs, metrics, and Delta table sink
dedupWindow-based, hash-based, and fuzzy deduplication strategies
diffDataFrame comparison with row-level, column-level, and schema diffs

20 files — every one fully type-hinted, tested, and documented.


Quick Start

1. Install

bash
pip install pyspark-utils-library

Or install from the source directory:

bash
pip install -e .

For fuzzy deduplication support (Levenshtein distance):

bash
pip install pyspark-utils-library[fuzzy]

For development (testing, linting, type checking):

bash
pip install pyspark-utils-library[dev]

2. Import and Use

python
from pyspark_utils.transformations import clean_column_names, add_metadata_columns
from pyspark_utils.data_quality import DQValidator
from pyspark_utils.scd import scd2_merge
from pyspark_utils.logging_utils import PipelineLogger

# Clean and enrich a raw DataFrame
df = spark.read.table("bronze.raw_orders")
df = clean_column_names(df)
df = add_metadata_columns(df, source="erp_system")

# Validate data quality
report = (
    DQValidator(df)
    .check_nulls(["order_id", "customer_id"])
    .check_unique(["order_id"])
    .check_range("amount", min_val=0.0)
    .validate()
)

# Log results
logger = PipelineLogger(pipeline_name="orders_silver")
logger.log_dq_report(report)

3. Use in Databricks

Upload the library to your Databricks workspace or install it as a cluster library:

/Repos/<your-user>/pyspark-utils-library/

All modules are compatible with Databricks Runtime 13.x+ and work inside notebooks, jobs, and DLT pipelines.


File Structure

pyspark-utils-library/
│
├── README.md                          # This file
├── LICENSE                            # MIT License
├── setup.py                           # Package configuration
├── pyproject.toml                     # Build system & tool config
│
├── pyspark_utils/
│   ├── __init__.py                    # Package init — version, public API exports
│   ├── transformations.py             # 15 reusable DataFrame transforms
│   ├── data_quality.py                # Chainable DQ validator with reporting
│   ├── scd.py                         # SCD Type 1 & Type 2 Delta Lake merges
│   ├── schema_utils.py                # Schema comparison, evolution, DDL tools
│   ├── logging_utils.py               # Structured JSON logging with Delta sink
│   ├── dedup.py                       # Window, hash, and fuzzy dedup strategies
│   └── diff.py                        # DataFrame comparison and diff reports
│
├── tests/
│   ├── __init__.py                    # Test package init
│   ├── conftest.py                    # SparkSession fixture & sample DataFrames
│   ├── test_transformations.py        # 25+ tests across 14 test classes
│   ├── test_data_quality.py           # 20+ tests across 9 test classes
│   ├── test_scd.py                    # 9 tests for SCD1 & SCD2
│   ├── test_schema_utils.py           # 20+ tests across 6 test classes
│   ├── test_dedup.py                  # 24+ tests across 4 test classes
│   └── test_diff.py                   # 22+ tests across 5 test classes
│
└── examples/
    └── usage_examples.py              # Databricks notebook-style examples for all modules

Chapter 2

Module Deep Dives

Follow this guide to get pyspark utility library up and running in your environment.

Module Deep Dives

transformations — 15 DataFrame Transforms

Reusable, composable functions that each take a DataFrame and return a new DataFrame. Designed to be chained using PySpark's .transform() method.

#### Column Cleaning

python
from pyspark_utils.transformations import clean_column_names, rename_columns_bulk

# Normalize all column names to lowercase snake_case
df = clean_column_names(df)
# "Order ID" → "order_id", "customer-name" → "customer_name"

# Bulk rename with a mapping dict
df = rename_columns_bulk(df, {"old_col": "new_col", "amt": "amount"})

#### Type Casting

python
from pyspark_utils.transformations import cast_columns, type_cast_dataframe

# Cast specific columns
df = cast_columns(df, {"amount": "double", "quantity": "integer"})

# Auto-cast all columns based on a target schema
df = type_cast_dataframe(df, target_schema)

#### Struct & Array Operations

python
from pyspark_utils.transformations import flatten_struct, explode_array

# Flatten nested structs into top-level columns (address.city → address_city)
df = flatten_struct(df)

# Explode an array column with position index
df = explode_array(df, array_column="line_items", position_alias="item_index")

#### Pivoting & Unpivoting

python
from pyspark_utils.transformations import pivot_aggregate, unpivot

# Pivot: rows → columns
df_pivoted = pivot_aggregate(
    df,
    group_columns=["customer_id"],
    pivot_column="metric_name",
    agg_column="metric_value",
    agg_function="sum",
)

# Unpivot: columns → rows
df_unpivoted = unpivot(
    df,
    id_columns=["customer_id"],
    value_columns=["revenue", "cost", "profit"],
    var_col_name="metric",
    val_col_name="value",
)

#### Metadata & Hashing

python
from pyspark_utils.transformations import (
    add_metadata_columns,
    hash_columns,
    add_surrogate_key,
)

# Add ETL metadata: _load_timestamp, _source, _filename
df = add_metadata_columns(df, source="salesforce")

# Create a SHA-256 hash column from selected columns
df = hash_columns(df, columns=["name", "email"], output_column="row_hash")

# Add a monotonically increasing surrogate key
df = add_surrogate_key(df, key_column="sk_customer")

#### String & Null Helpers

python
from pyspark_utils.transformations import (
    trim_strings,
    coalesce_columns,
    filter_nulls,
    standardize_timestamps,
)

# Trim whitespace from all string columns
df = trim_strings(df)

# Coalesce multiple columns into one (first non-null wins)
df = coalesce_columns(df, columns=["phone_mobile", "phone_work", "phone_home"], output="phone")

# Filter rows where any/all specified columns are null
df = filter_nulls(df, columns=["order_id", "amount"], how="any")

# Parse and standardize mixed timestamp formats to a consistent format
df = standardize_timestamps(df, columns=["created_at", "updated_at"])

data_quality — Chainable Validation Framework

A fluent validation API that accumulates rules and produces a structured DQReport with pass/fail/warning counts.

#### Basic Validation

python
from pyspark_utils.data_quality import DQValidator

report = (
    DQValidator(df)
    .check_nulls(["order_id", "customer_id"])
    .check_unique(["order_id"])
    .check_range("amount", min_val=0.0, max_val=1_000_000.0)
    .check_regex("email", r"^[\w.+-]+@[\w-]+\.[\w.]+$")
    .validate()
)

print(report)
# DQReport(total=5, passed=4, failed=1, warnings=0, ...)

#### Referential Integrity

python
report = (
    DQValidator(orders_df)
    .check_referential_integrity(
        column="customer_id",
        reference_df=customers_df,
        reference_column="id",
    )
    .validate()
)

#### Schema Validation

python
from pyspark.sql.types import StructType, StructField, StringType, DoubleType

expected_schema = StructType([
    StructField("order_id", StringType(), False),
    StructField("amount", DoubleType(), True),
])

report = (
    DQValidator(df)
    .check_schema(expected_schema)
    .validate()
)

#### Custom Rules

python
report = (
    DQValidator(df)
    .check_custom(
        name="positive_margin",
        rule_fn=lambda d: d.filter(F.col("margin") < 0).count() == 0,
        description="All margins must be non-negative",
    )
    .validate()
)

#### Row Count Validation

python
report = (
    DQValidator(df)
    .check_row_count(min_count=1000, max_count=10_000_000)
    .validate()
)

#### Fail-Fast Mode

python
from pyspark_utils.data_quality import DQValidator

# Raises ValueError immediately on first failure
DQValidator(df).check_nulls(["order_id"]).validate_or_raise()

#### Working with Reports

python
report = DQValidator(df).check_nulls(["order_id"]).validate()

# Structured access
print(report.passed)      # Number of passed checks
print(report.failed)      # Number of failed checks
print(report.is_healthy)  # True if zero failures

# Iterate individual results
for result in report.results:
    print(f"{result.rule_name}: {result.status} — {result.detail}")

# Serialize to JSON for logging
print(report.to_json())

scd — Slowly Changing Dimensions for Delta Lake

Production-ready SCD implementations that use Delta Lake MERGE operations.

#### SCD Type 2 (Full History)

Maintains a complete history of changes. Each record gets effective_date, expiry_date, and is_current columns.

python
from pyspark_utils.scd import scd2_merge

scd2_merge(
    spark=spark,
    source_df=new_customers,
    target_table="gold.dim_customer",
    key_columns=["customer_id"],
    tracked_columns=["name", "email", "address"],
)

After the merge:

  • New customers are inserted with is_current=True and expiry_date=9999-12-31
  • Changed customers: the old row is expired (closed), a new row is inserted as current
  • Unchanged customers are left untouched

#### Change Detection

Inspect what would change before running the merge:

python
from pyspark_utils.scd import detect_changes

changes = detect_changes(
    source_df=new_data,
    target_df=spark.read.table("gold.dim_customer"),
    key_columns=["customer_id"],
    tracked_columns=["name", "email"],
)

print(f"New records: {changes['inserts'].count()}")
print(f"Changed records: {changes['updates'].count()}")
print(f"Unchanged records: {changes['unchanged'].count()}")

#### Initialize a History Table

Create an SCD2 table from scratch with the required metadata columns:

python
from pyspark_utils.scd import create_history_table

create_history_table(
    spark=spark,
    source_df=initial_load,
    target_table="gold.dim_product",
    key_columns=["product_id"],
)

#### Close Expired Records

Manually expire records that match a condition:

python
from pyspark_utils.scd import close_expired_records

close_expired_records(
    spark=spark,
    target_table="gold.dim_customer",
    key_column_values={"customer_id": ["CUST-001", "CUST-002"]},
)

#### SCD Type 1 (Overwrite)

Simple upsert — overwrites existing records with new values:

python
from pyspark_utils.scd import scd1_overwrite

scd1_overwrite(
    spark=spark,
    source_df=updated_products,
    target_table="gold.dim_product",
    key_columns=["product_id"],
)

schema_utils — Schema Comparison & Evolution

Tools for handling schema drift and evolution in production pipelines.

#### Compare Two Schemas

python
from pyspark_utils.schema_utils import compare_schemas

result = compare_schemas(old_schema, new_schema)
print(result.added)           # ["new_column"]
print(result.removed)         # ["deprecated_col"]
print(result.type_changed)    # {"amount": ("string", "double")}
print(result.is_compatible)   # False (breaking changes detected)

#### Evolve a DataFrame to Match a Target Schema

python
from pyspark_utils.schema_utils import evolve_schema

# Adds missing columns (as null) and reorders to match target
evolved_df = evolve_schema(df, target_schema, fill_new_with_null=True)

#### Schema ↔ DDL Conversion

python
from pyspark_utils.schema_utils import schema_to_ddl, ddl_to_schema

# Convert StructType to DDL string (useful for CREATE TABLE)
ddl_string = schema_to_ddl(df.schema)
# "order_id STRING NOT NULL, amount DOUBLE, created_at TIMESTAMP"

# Parse DDL back to StructType
schema = ddl_to_schema("order_id STRING, amount DOUBLE")

#### Validate Compatibility

python
from pyspark_utils.schema_utils import validate_schema_compatibility

is_ok = validate_schema_compatibility(
    source_schema=incoming_df.schema,
    target_schema=spark.read.table("silver.orders").schema,
)
# Returns True if source can safely write to target

#### Merge Multiple Schemas

python
from pyspark_utils.schema_utils import merge_schemas

# Union of all fields from multiple schemas
merged = merge_schemas([schema_a, schema_b, schema_c])

logging_utils — Structured Pipeline Logging

Production logging with correlation IDs, metric tracking, and optional Delta table persistence.

#### Basic Logging

python
from pyspark_utils.logging_utils import PipelineLogger

logger = PipelineLogger(pipeline_name="orders_silver")
logger.info("Pipeline started", extra={"batch_date": "2026-01-15"})
logger.warning("Null rate above threshold", extra={"column": "email", "pct": 0.12})
logger.error("Schema mismatch detected")

All log entries include:

  • ISO-8601 timestamp
  • Pipeline name
  • Correlation ID (auto-generated UUID, consistent for the entire pipeline run)
  • Structured JSON output compatible with Azure Monitor, ELK, and Splunk

#### Metric Tracking

python
logger.track_metric("row_count", 125_000)
logger.track_metric("null_pct_order_id", 0.001)
logger.track_metric("processing_time_seconds", 42.5)

# Retrieve tracked metrics
metrics = logger.get_metrics()
# {"row_count": 125000, "null_pct_order_id": 0.001, ...}

#### Threshold Alerting

python
# Alert if a metric exceeds bounds
logger.alert_on_threshold("null_pct_order_id", max_val=0.05)
# No alert — 0.001 < 0.05

logger.track_metric("null_pct_email", 0.12)
logger.alert_on_threshold("null_pct_email", max_val=0.05)
# WARNING: null_pct_email (0.12) exceeds max threshold 0.05

#### Delta Table Sink

python
# Persist all logs to a Delta table
logger = PipelineLogger(
    pipeline_name="orders_silver",
    log_to_delta_table="audit.pipeline_logs",
)
# Logs are buffered and flushed to Delta on close or explicit flush

logger.info("Processing batch")
logger.flush_to_delta()  # Explicit flush

#### Context Manager Support

python
with PipelineLogger(pipeline_name="orders_silver") as logger:
    logger.info("Starting")
    # ... pipeline logic ...
    logger.info("Done")
# Automatically flushes on exit

dedup — Multiple Deduplication Strategies

Four approaches to deduplication, each suited to different use cases.

#### Window Dedup (Recommended)

Uses ROW_NUMBER() to keep the preferred record per key group. Best for most use cases.

python
from pyspark_utils.dedup import window_dedup

deduped = window_dedup(
    df,
    key_columns=["customer_id"],
    order_columns=[("updated_at", "desc")],
    keep="first",  # Keep the most recent per customer
)

#### Hash Dedup

Generates a content hash from all (or selected) columns and removes exact duplicates:

python
from pyspark_utils.dedup import hash_dedup

deduped = hash_dedup(
    df,
    columns=["name", "email", "phone"],  # Hash only these columns
)

#### Fuzzy Dedup

Approximate matching using Levenshtein edit distance. Useful for deduplicating messy string data like names and addresses. Requires the fuzzy extra.

python
from pyspark_utils.dedup import fuzzy_dedup

deduped = fuzzy_dedup(
    df,
    key_column="customer_name",
    threshold=2,  # Max edit distance to consider a match
    group_columns=["city"],  # Reduce comparison space
)

#### Dedup Report

Produce a summary of duplicates without removing them — useful for data profiling:

python
from pyspark_utils.dedup import dedup_report

report = dedup_report(df, key_columns=["customer_id"])
report.show()
# +-------------+-----+
# | customer_id | count|
# +-------------+-----+
# | CUST-001    |    3 |
# | CUST-042    |    2 |
# +-------------+-----+

diff — DataFrame Comparison

Compare two DataFrames to understand what changed. Essential for pipeline testing, data validation, and regression checks.

#### Full Row-Level Diff

python
from pyspark_utils.diff import df_diff

result = df_diff(
    left=df_expected,
    right=df_actual,
    key_columns=["order_id"],
)

result["added"].show()      # Rows in actual but not expected
result["removed"].show()    # Rows in expected but not actual
result["changed"].show()    # Rows with same key but different values
result["unchanged"].show()  # Identical rows

summary = result["summary"]
print(summary)
# DiffSummary(left=1000, right=1005, added=10, removed=5, changed=3, unchanged=992)

#### Column-Level Diff

See which specific columns changed for matching rows:

python
from pyspark_utils.diff import column_diff

changes = column_diff(
    left=df_before,
    right=df_after,
    key_columns=["customer_id"],
)
changes.show()
# +-------------+--------+----------+-----------+
# | customer_id | column | old_value| new_value |
# +-------------+--------+----------+-----------+
# | CUST-001    | email  | old@x.co | new@x.co  |
# | CUST-001    | phone  | 555-0001 | 555-0099  |
# +-------------+--------+----------+-----------+

#### Schema Diff

Compare schemas between two DataFrames:

python
from pyspark_utils.diff import schema_diff

result = schema_diff(df_v1, df_v2)
print(result.added)          # Columns added in v2
print(result.removed)        # Columns removed in v2
print(result.type_changed)   # Columns with type changes

#### Row Count Comparison

Quick sanity check between pipeline stages:

python
from pyspark_utils.diff import row_count_comparison

result = row_count_comparison(
    left=bronze_df,
    right=silver_df,
    labels=("bronze", "silver"),
)
print(result)
# RowCountResult(bronze=100000, silver=99850, difference=-150, pct_change=-0.15)

#### Value Distribution Diff

Compare column value distributions across two DataFrames:

python
from pyspark_utils.diff import value_distribution_diff

dist = value_distribution_diff(
    left=df_january,
    right=df_february,
    column="order_status",
)
dist.show()
# +-----------+----------+-----------+--------+
# | value     | left_pct | right_pct | change |
# +-----------+----------+-----------+--------+
# | completed |     0.85 |      0.82 |  -0.03 |
# | pending   |     0.10 |      0.14 |  +0.04 |
# | cancelled |     0.05 |      0.04 |  -0.01 |
# +-----------+----------+-----------+--------+

API Reference Summary

transformations

FunctionDescription
clean_column_names(df)Normalize columns to lowercase snake_case
rename_columns_bulk(df, mapping)Rename columns with a {old: new} mapping
cast_columns(df, column_types)Cast specific columns to target types
type_cast_dataframe(df, target_schema)Cast all columns to match a target schema
flatten_struct(df)Flatten nested struct columns to top-level
explode_array(df, array_column, ...)Explode an array column with position index
pivot_aggregate(df, group_columns, ...)Pivot rows into columns with aggregation
unpivot(df, id_columns, value_columns, ...)Unpivot columns into key-value rows
add_metadata_columns(df, source, ...)Add ETL metadata columns
standardize_timestamps(df, columns, ...)Normalize timestamp formats
trim_strings(df)Trim whitespace from all string columns
coalesce_columns(df, columns, output)Merge columns using first non-null value
filter_nulls(df, columns, how)Filter rows with null values
hash_columns(df, columns, output_column)Create SHA-256 hash from columns
add_surrogate_key(df, key_column)Add monotonically increasing surrogate key

data_quality

Class / FunctionDescription
DQValidator(df)Chainable validator — call .check_*() methods, then .validate()
.check_nulls(columns)Verify columns contain no null values
.check_unique(columns)Verify columns contain unique values
.check_referential_integrity(...)Check FK relationship against reference DataFrame
.check_range(column, min_val, max_val)Validate numeric range
.check_regex(column, pattern)Validate string format with regex
.check_schema(expected_schema)Compare actual schema to expected
.check_row_count(min_count, max_count)Validate row count bounds
.check_custom(name, rule_fn, ...)Add a custom validation rule
.validate()Execute all rules and return DQReport
.validate_or_raise()Execute and raise ValueError on first failure
DQReportResult container: passed, failed, is_healthy, to_json()
DQRuleRule definition dataclass
DQResultIndividual check result
DQSeverityEnum: ERROR, WARNING, INFO
DQStatusEnum: PASSED, FAILED, WARNING

scd

FunctionDescription
scd2_merge(spark, source_df, target_table, ...)Full SCD Type 2 merge with Delta Lake
detect_changes(source_df, target_df, ...)Compare source/target to find inserts, updates, unchanged
create_history_table(spark, source_df, ...)Initialize an SCD2 table with metadata columns
close_expired_records(spark, target_table, ...)Manually expire specific records
scd1_overwrite(spark, source_df, ...)SCD Type 1 upsert (overwrite in place)

schema_utils

Function / ClassDescription
compare_schemas(old, new)Compare two schemas — returns SchemaComparisonResult
evolve_schema(df, target_schema, ...)Add missing columns and reorder to match target
schema_to_ddl(schema)Convert StructType to DDL string
ddl_to_schema(ddl_string)Parse DDL string to StructType
validate_schema_compatibility(source, target)Check if source can safely write to target
merge_schemas(schemas)Union of fields from multiple schemas
SchemaComparisonResultResult dataclass: added, removed, type_changed, is_compatible

logging_utils

Class / MethodDescription
PipelineLogger(pipeline_name, ...)Main logger — creates correlation ID, sets up handlers
.info(message, extra)Log at INFO level
.warning(message, extra)Log at WARNING level
.error(message, extra)Log at ERROR level
.track_metric(name, value)Record a numeric KPI
.get_metrics()Return all tracked metrics as a dict
.alert_on_threshold(metric, min_val, max_val)Alert if metric exceeds bounds
.log_dq_report(report)Log a DQReport as structured entries
.flush_to_delta(table_name)Write buffered logs to a Delta table

dedup

FunctionDescription
window_dedup(df, key_columns, order_columns, ...)ROW_NUMBER-based dedup — keep first/last per group
hash_dedup(df, columns)Remove exact duplicates via content hashing
fuzzy_dedup(df, key_column, threshold, ...)Approximate matching using edit distance
dedup_report(df, key_columns)Report duplicate counts without removing rows

diff

Function / ClassDescription
df_diff(left, right, key_columns)Full row-level diff — added, removed, changed, unchanged
column_diff(left, right, key_columns)Column-level change details for matching rows
schema_diff(left, right)Compare schemas between two DataFrames
row_count_comparison(left, right, labels)Quick row count comparison with pct change
value_distribution_diff(left, right, column)Compare value distributions in a column
DiffSummarySummary dataclass with has_differences property
SchemaDiffResultSchema diff result dataclass
RowCountResultRow count comparison result dataclass

Common Patterns

Bronze → Silver Pipeline

python
from pyspark_utils.transformations import clean_column_names, trim_strings, add_metadata_columns
from pyspark_utils.data_quality import DQValidator
from pyspark_utils.dedup import window_dedup
from pyspark_utils.logging_utils import PipelineLogger

with PipelineLogger(pipeline_name="orders_bronze_to_silver") as logger:
    # Read bronze
    df = spark.read.table("bronze.raw_orders")
    logger.track_metric("bronze_row_count", df.count())

    # Transform
    df = clean_column_names(df)
    df = trim_strings(df)
    df = add_metadata_columns(df, source="erp")

    # Deduplicate
    df = window_dedup(
        df,
        key_columns=["order_id"],
        order_columns=[("_load_timestamp", "desc")],
    )

    # Validate
    report = (
        DQValidator(df)
        .check_nulls(["order_id", "customer_id", "amount"])
        .check_unique(["order_id"])
        .check_range("amount", min_val=0.0)
        .validate()
    )
    logger.log_dq_report(report)
    logger.track_metric("silver_row_count", df.count())

    # Write silver
    df.write.format("delta").mode("overwrite").saveAsTable("silver.orders")
    logger.info("Silver table written successfully")

Silver → Gold Dimension with SCD2

python
from pyspark_utils.scd import scd2_merge
from pyspark_utils.schema_utils import compare_schemas
from pyspark_utils.logging_utils import PipelineLogger

with PipelineLogger(pipeline_name="dim_customer_scd2") as logger:
    # Read silver
    silver_df = spark.read.table("silver.customers")

    # Check for schema drift
    target_schema = spark.read.table("gold.dim_customer").schema
    diff = compare_schemas(target_schema, silver_df.schema)
    if not diff.is_compatible:
        logger.warning("Schema drift detected", extra={"added": diff.added})

    # Merge with SCD2
    scd2_merge(
        spark=spark,
        source_df=silver_df,
        target_table="gold.dim_customer",
        key_columns=["customer_id"],
        tracked_columns=["name", "email", "address", "phone"],
    )
    logger.info("SCD2 merge completed")

Data Quality Gate

python
from pyspark_utils.data_quality import DQValidator

# Use validate_or_raise to halt the pipeline on failure
DQValidator(df) \
    .check_nulls(["order_id"]) \
    .check_unique(["order_id"]) \
    .check_row_count(min_count=1000) \
    .check_range("amount", min_val=0.0) \
    .validate_or_raise()

# If we reach here, all checks passed
df.write.format("delta").mode("overwrite").saveAsTable("silver.orders")

Chapter 3
🔒 Available in full product

Testing

Chapter 4
🔒 Available in full product

Advanced Reference

You’ve reached the end of the free preview

Get the full PySpark Utility Library and unlock everything.

All Chapters

Get the complete guide with every chapter unlocked, including code samples, diagrams, and best practices.

Full Tool Suite

Access all interactive tools with complete data, all workload profiles, and the full scenario library.

Source Files

Downloadable source code, configuration files, and working examples from every chapter.

Lifetime Updates

Free updates for life. Every new chapter, tool, and improvement included.

Buy Now — $29 →
📦 Free sample included — download another copy for the full product.
PySpark Utility Library v1.0.0 — Free Preview