Seed the Project - Data Generator and Loader Notebook

Estimated read: 8 min

Lecture 4 set the targets; this lecture writes the code – a local Python script that generates batch zero with Faker, and a Databricks notebook that loads it into dev.step_right.

The generator script

scripts/generate_batch.py runs locally (not on a Databricks cluster) and writes plain CSV/JSON files – deliberately outside Databricks, since a real source system doesn’t run inside your Lakehouse either:

# scripts/generate_batch.py
import csv
import json
import random
import uuid
from datetime import datetime, timedelta
from faker import Faker

fake = Faker()
Faker.seed(42)  # reproducible batch zero across runs

CATEGORIES = ["Running", "Trail", "Basketball", "Casual", "Training", "Sandals"]

def generate_customers(n=2000):
    customers = []
    for i in range(n):
        customers.append({
            "customer_id": f"CUST-{i:06d}",
            "first_name": fake.first_name(),
            "last_name": fake.last_name(),
            # ~2% missing emails, matching Lecture 4's "realistic imperfection" target
            "email": fake.email() if random.random() > 0.02 else None,
            "region": random.choice(["NORTHEAST", "SOUTHEAST", "MIDWEST", "WEST"]),
            "signup_date": fake.date_between(start_date="-3y", end_date="today").isoformat(),
            "updated_at": datetime.utcnow().isoformat(),
        })
    return customers

def generate_products(n=400):
    products = []
    for i in range(n):
        products.append({
            "product_id": f"PROD-{i:05d}",
            "product_name": f"{fake.word().title()} {random.choice(CATEGORIES)} Shoe",
            "category": random.choice(CATEGORIES),
            "list_price": round(random.uniform(39.99, 189.99), 2),
            "launch_date": fake.date_between(start_date="-2y", end_date="today").isoformat(),
        })
    return products

def generate_orders_and_items(customers, products, n_orders=12000):
    orders, items = [], []
    for i in range(n_orders):
        order_id = f"ORD-{i:07d}"
        # ~1% reference a customer not yet in the feed -- CDC ordering/timing gap
        cust = random.choice(customers)["customer_id"] if random.random() > 0.01 else "CUST-999999"
        orders.append({
            "order_id": order_id,
            "customer_id": cust,
            "order_date": fake.date_between(start_date="-6M", end_date="today").isoformat(),
            "status": random.choice(["PLACED", "SHIPPED", "DELIVERED", "CANCELLED"]),
            "updated_at": datetime.utcnow().isoformat(),
        })
        for _ in range(random.randint(1, 4)):
            # ~2% reference a product not yet in the catalog -- late catalog update
            prod = random.choice(products)["product_id"] if random.random() > 0.02 else "PROD-99999"
            items.append({
                "order_item_id": str(uuid.uuid4()),
                "order_id": order_id,
                "product_id": prod,
                "quantity": random.randint(1, 3),
                # occasional negative price, exercising Section 3's DQ rules
                "unit_price": round(random.uniform(39.99, 189.99), 2) if random.random() > 0.005 else -9.99,
                "discount_pct": round(random.uniform(0, 30), 1),
            })
    return orders, items

if __name__ == "__main__":
    customers = generate_customers()
    products = generate_products()
    orders, items = generate_orders_and_items(customers, products)

    for name, rows in [("customers", customers), ("products", products),
                        ("orders", orders), ("order_items", items)]:
        with open(f"batch_zero_{name}.json", "w") as f:
            json.dump(rows, f)
    print(f"Batch zero: {len(customers)} customers, {len(products)} products, "
          f"{len(orders)} orders, {len(items)} order_items")

Running python scripts/generate_batch.py produces four JSON files locally – the deliberate imperfections from Lecture 4’s plan (missing emails, orphaned foreign keys, negative prices) are baked in at generation time via the random.random() > threshold checks, not added later.

Uploading to the staging volume

Upload the generated files to dev.step_right.staging (create it first, the same way Lecture 3 created landing) using the Databricks CLI, standing in for however a real source system would drop files onto a shared location:

databricks fs cp batch_zero_customers.json dbfs:/Volumes/dev/step_right/staging/customers/batch_zero.json
databricks fs cp batch_zero_products.json dbfs:/Volumes/dev/step_right/staging/products/batch_zero.json
databricks fs cp batch_zero_orders.json dbfs:/Volumes/dev/step_right/staging/orders/batch_zero.json
databricks fs cp batch_zero_order_items.json dbfs:/Volumes/dev/step_right/staging/order_items/batch_zero.json

The loader notebook

notebooks/load_batch_to_landing.py runs inside Databricks and does two things: for the two file-shaped sources bronze pipelines will read directly (products), it copies straight into the matching landing subfolder; for the CDC-shaped sources (customers, orders, order_items), it writes into dev.raw_cdc, simulating what the managed CDC connector would have produced:

# notebooks/load_batch_to_landing.py
dbutils.widgets.text("batch_name", "batch_zero")
batch_name = dbutils.widgets.get("batch_name")

# File-shaped source: straight copy into the landing volume
dbutils.fs.cp(
    f"/Volumes/dev/step_right/staging/products/{batch_name}.json",
    f"/Volumes/dev/step_right/landing/products/{batch_name}.json",
)

# CDC-shaped sources: land as governed tables under dev.raw_cdc
for source in ["customers", "orders", "order_items"]:
    df = (
        spark.read.json(f"/Volumes/dev/step_right/staging/{source}/{batch_name}.json")
    )
    df.write.mode("append").saveAsTable(f"dev.raw_cdc.{source}_changefeed")

print(f"Loaded {batch_name} into landing volume and dev.raw_cdc")

Parameterizing on batch_name rather than hardcoding batch_zero means this same notebook loads every incremental batch Lecture 4 planned for later sections – run it again with batch_name=batch_one once Section 2’s pipelines exist and need something new to process.

Verify the seed landed

SELECT COUNT(*) FROM dev.raw_cdc.customers_changefeed;
SELECT COUNT(*) FROM dev.raw_cdc.orders_changefeed;
SELECT COUNT(*) FROM dev.raw_cdc.order_items_changefeed;
LIST '/Volumes/dev/step_right/landing/products/';

With real (if imperfect) rows in dev.raw_cdc and a first file in the landing volume, Section 2 can build its first bronze pipeline against actual data instead of an empty schema.


| ← Previous: Test Data Strategy - Planning your Test Data Preparation | Next: Download Project Source Code → | |:—|—:|


Back to top

Independent, self-authored Databricks companion notes.

This site uses Just the Docs, a documentation theme for Jekyll.