Lesson 01 Python ETL Fundamentals
Theme:
Lesson 01 · Slide 1

Lesson 01 — Python ETL Fundamentals

From a URL on the internet to a CSV on your disk in 50 lines.

Data Engineering Module 1
Lesson 01 · Slide 2

Extract → Transform → Load.

Every data pipeline you'll ever write is some version of those three verbs. Today's pipeline is the simplest possible one — three Python functions and a __main__.

ETL vs ELT

ETL vs ELT: same three letters, swapped order. Modern lakehouses prefer ELT. We'll cover both.

Lesson 01 · Slide 3

One hour of GitHub, every hour, since 2011.

One event looks like:

{
  "id": "12345",
  "type": "PushEvent",
  "actor": {"login": "octocat"},
  "repo":  {"name": "octocat/hello"},
  "created_at": "2024-01-01T15:00:00Z"
}
Lesson 01 · Slide 4

Count events by type for one hour.

50 lines of Python. We'll keep coming back to this script for the rest of Module 1.

Lesson 01 · Slide 5

Extract — the download

import requests, gzip, json, pathlib

URL = "https://data.gharchive.org/{hour}.json.gz"

def fetch_hour(hour: str, out_dir: pathlib.Path) -> pathlib.Path:
    out_dir.mkdir(parents=True, exist_ok=True)
    dest = out_dir / f"{hour}.json.gz"
    if dest.exists():
        return dest                      # poor man's caching
    resp = requests.get(URL.format(hour=hour), timeout=30)
    resp.raise_for_status()
    dest.write_bytes(resp.content)
    return dest

Notice: we cache by hour. Re-running is free. This is the first hint of idempotency — Lesson 06's whole topic.

Lesson 01 · Slide 6

Transform — parse + count

from collections import Counter

def count_by_type(path: pathlib.Path) -> Counter:
    counts = Counter()
    with gzip.open(path, "rt") as f:
        for line in f:
            ev = json.loads(line)
            counts[ev["type"]] += 1
    return counts

Use Counter, not dict. Use a streaming reader (for line in f) not f.read() — gharchive hours can spike to 200 MB.

Lesson 01 · Slide 7

Load — write a CSV

import csv

def write_csv(counts: Counter, path: pathlib.Path) -> None:
    with path.open("w", newline="") as f:
        w = csv.writer(f)
        w.writerow(["type", "count"])
        for evtype, n in counts.most_common():
            w.writerow([evtype, n])
Lesson 01 · Slide 8

Wire it together

if __name__ == "__main__":
    import sys
    hour = sys.argv[1]                    # e.g. "2024-01-01-15"
    raw = fetch_hour(hour, pathlib.Path("data/raw"))
    counts = count_by_type(raw)
    write_csv(counts, pathlib.Path(f"data/out/{hour}.csv"))
    print(f"wrote {sum(counts.values())} events across {len(counts)} types")
Lesson 01 · Slide 9

What you'd see on a real hour

typecount
PushEvent45,212
CreateEvent12,103
PullRequestEvent8,940
WatchEvent7,712
......
Lesson 01 · Slide 10

The 6 things our 50 lines did right

Each of those is a lesson on its own — we'll deepen them across Module 1.

Lesson 01 · Slide 11

What our 50 lines did wrong (and we'll fix)

  • No schema validation (L03)
  • No retries (L06)
  • No partitioning (L02)
  • CSV is the wrong format (L02)
  • No observability (L12)

That's literally the rest of the course. Each lesson erases one item from this list.

Lesson 01 · Slide 12

Where it lives in the repo

data-engineering/sample-pipeline/
  extract/gharchive.py        # fetch_hour
  transform/events.py         # count_by_type, write_csv
  requirements.txt

Today you'll fill the NotImplementedError stubs that Plan A scaffolded.

Lesson 01 · Slide 13

A word on "ETL vs ELT vs streaming"

We do batch ETL today. The modern lakehouse prefers ELT (load first, transform on the warehouse) — that pattern is covered in Module 3 when we introduce dbt and a cloud warehouse.

Streaming (Kafka, Flink) is a sibling discipline and intentionally not in this course. It requires a different runtime model; treating it as "just faster batch" is one of the classic mistakes in data engineering. Our gharchive pipeline is batch by design — the data is already hourly.

Lesson 01 · Slide 14

Recap & what's next

Coming up: Lesson 02 — why CSV was the wrong format and what Parquet does about it.
1 / ?