Lesson 03 Data Quality with Great Expectations
Theme:

Lab 03 — Build a Great Expectations suite for gharchive events

Lesson 03 · Data Engineering with Python · ~45 min

By the end of this lab you will have a runnable GE checkpoint that reads the L02 Parquet output, runs eleven expectations, and writes an HTML report. You will also intentionally break the data and observe what the failure report looks like — the most important thing you can do to build intuition for a new tool.

What you need

Step 1 — Initialise GE in the sample-pipeline

Navigate to the sample-pipeline directory and run the GE init command:

cd data-engineering/sample-pipeline
source .venv/bin/activate       # or however you activate the venv
great_expectations init

GE will ask you a few questions about your data source. Answer "no" to the interactive wizard prompts for now — we will configure the datasource in code. After the command completes you should have a gx/ folder containing:

gx/
  great_expectations.yml
  expectations/
  checkpoints/
  uncommitted/
    config_variables.yml
    data_docs/

Step 2 — Implement build_suite in quality/expectations.py

Open data-engineering/sample-pipeline/quality/expectations.py. Replace the NotImplementedError body with the following implementation:

"""Great Expectations suite for the gharchive events table."""
from __future__ import annotations

import pathlib
import great_expectations as gx
from great_expectations.core.batch import RuntimeBatchRequest

KNOWN_EVENT_TYPES = [
    "PushEvent", "PullRequestEvent", "IssuesEvent",
    "WatchEvent", "ForkEvent", "CreateEvent", "DeleteEvent",
    "ReleaseEvent", "PublicEvent", "MemberEvent", "GollumEvent",
    "CommitCommentEvent", "IssueCommentEvent",
    "PullRequestReviewEvent", "PullRequestReviewCommentEvent",
    "DiscussionEvent",
]

SUITE_NAME = "gharchive_events"


def build_suite(parquet_dir: str | pathlib.Path) -> None:
    """Build and save the starter expectation suite.

    Args:
        parquet_dir: Path to a directory of .parquet files to validate against.
    """
    context = gx.get_context()

    # Create a Pandas datasource
    datasource = context.sources.add_or_update_pandas("gharchive")
    asset = datasource.add_parquet_asset(
        name="events",
        batching_regex=r".*\.parquet",
    )
    batch_request = asset.build_batch_request()

    # Get a validator to build the suite
    validator = context.get_validator(
        batch_request=batch_request,
        expectation_suite_name=SUITE_NAME,
    )

    # --- Schema expectations (block-the-pipeline tier) ---
    for col in ("type", "actor", "repo", "created_at"):
        validator.expect_column_to_exist(col)
        validator.expect_column_values_to_not_be_null(col)

    # --- Value expectations (warn-but-load tier) ---
    validator.expect_column_values_to_be_in_set("type", KNOWN_EVENT_TYPES)
    validator.expect_column_values_to_match_strftime_format(
        "created_at", strftime_format="%Y-%m-%dT%H:%M:%SZ"
    )

    # --- Volume expectation (info tier) ---
    validator.expect_table_row_count_to_be_between(
        min_value=1_000, max_value=500_000
    )

    # Save the suite
    validator.save_expectation_suite(discard_failed_expectations=False)
    print(f"Suite '{SUITE_NAME}' saved.")


if __name__ == "__main__":
    import sys
    parquet_dir = sys.argv[1] if len(sys.argv) > 1 else "data/parquet/date=2024-01-01"
    build_suite(parquet_dir)

Run it to save the suite:

python quality/expectations.py data/parquet/date=2024-01-01

Expected output: Suite 'gharchive_events' saved. and a new file at gx/expectations/gharchive_events.json.

Step 3 — Wire a checkpoint

Create a checkpoint configuration file at gx/checkpoints/gharchive-events.yml:

name: gharchive-events
config_version: 1.0
class_name: SimpleCheckpoint
validations:
  - batch_request:
      datasource_name: gharchive
      data_asset_name: events
      options:
        path: data/parquet/date=2024-01-01
    expectation_suite_name: gharchive_events

Alternatively, run the interactive wizard:

great_expectations checkpoint new gharchive-events

Then configure it to point at the gharchive datasource, the events asset, and the gharchive_events suite.

Step 4 — Run the checkpoint

great_expectations checkpoint run gharchive-events

Expected output: a summary showing all expectations passing, followed by a line like:

Validation Succeeded!
  Evaluated 11 expectations
  11 passed, 0 failed

GE also writes a full HTML report to:

gx/uncommitted/data_docs/local_site/index.html

Step 5 — Break it on purpose

This is the most instructive step. Open Python and write a Parquet file with a deliberately bad row:

import pandas as pd, pathlib

# Read an existing hour
df = pd.read_parquet("data/parquet/date=2024-01-01/")

# Inject a row with an unknown event type
bad_row = df.iloc[0:1].copy()
bad_row["type"] = "ElonMuskEvent"
df_bad = pd.concat([df, bad_row], ignore_index=True)

# Save to a new test directory
out = pathlib.Path("data/parquet/date=2024-01-01-bad")
out.mkdir(parents=True, exist_ok=True)
df_bad.to_parquet(out / "events.parquet", index=False)
print(f"Written {len(df_bad)} rows with 1 bad type value")

Now update your checkpoint's path to point at data/parquet/date=2024-01-01-bad and re-run:

great_expectations checkpoint run gharchive-events

Expected: the expect_column_values_to_be_in_set expectation fails. The output shows the unexpected count (1) and the partial unexpected list (["ElonMuskEvent"]).

Step 6 — Open the data docs

open gx/uncommitted/data_docs/local_site/index.html

Browse the per-expectation results. Click on the failed expectation to see the sample of offending values. Click on "Validation History" to see the run that passed followed by the run that failed. This is what GE's data docs look like in production — a living record of every validation run.

What you should leave with

Troubleshooting

Common issues

great_expectations: command not found: the venv is not activated. Run source .venv/bin/activate (or your OS equivalent) and retry.

DatasourceError: unable to read parquet files: the path in your checkpoint YAML does not resolve to actual .parquet files. Use an absolute path or verify the relative path from the sample-pipeline/ directory. Run ls data/parquet/date=2024-01-01/ to confirm files exist.

GE v1 docs disagree with what you see: the course pins great-expectations>=0.18,<1. Follow the v0.18 docs at docs.greatexpectations.io/docs/0.18/. The v1 API (DataContext, Assets, Fluent datasources) is intentionally not covered until the schema-evolution lesson.

SuiteNotFoundError when running the checkpoint: the suite name in the checkpoint YAML must match the suite name passed to get_validator exactly. Both should be gharchive_events.