Every Monday, an analyst downloads three exports, cleans them in a spreadsheet and pastes them into a tracking file. One copy-paste mistake, and the executive committee receives wrong figures. An ETL pipeline automates this work in a reliable, reproducible way.
The three steps
- Extract: read the data where it lives (CSV files, API, production database) without modifying it.
- Transform: clean, type, normalize, join, aggregate, compute indicators.
- Load: write the result to the destination (data warehouse, analytical database, file).
ETL or ELT?
In ETL, you transform before loading. In ELT, you first load the raw data into the warehouse, then transform it in SQL directly inside the warehouse. ELT has become widespread with powerful cloud warehouses and tools like dbt: you keep the raw history and can replay the transformations. The principles in this course hold for both: only the steps change place.
Splitting into functions
A readable pipeline is made of small single-responsibility functions:
import io
import pandas as pd
def extraire(texte_csv):
# reads the source without modifying anything
return pd.read_csv(io.StringIO(texte_csv))
def transformer(df):
# pure function: does not modify its input, returns a new DataFrame
sortie = df.copy()
sortie["client"] = sortie["client"].str.strip().str.lower()
sortie = sortie[sortie["montant"] > 0]
return sortie.groupby("client", as_index=False)["montant"].sum()
def charger(df, destination):
# fully replaces the contents of the destination
destination.clear()
destination.extend(df.to_dict("records"))
source = "client,montant\n Alice ,10\nbob,5\nalice,7\nbob,-2\n"
entrepot = [] # a list plays the role of the destination here
charger(transformer(extraire(source)), entrepot)
print(entrepot) # [{'client': 'alice', 'montant': 17}, {'client': 'bob', 'montant': 5}]
A pure function always returns the same result for the same input and changes nothing around it. You can test it with a small DataFrame, with no database or network.
Idempotency
A pipeline will be rerun: after an outage, a fix, a backfill. It must be idempotent: rerunning it on the same data produces the same final state, with no duplicates.
charger(transformer(extraire(source)), entrepot) # second run
print(len(entrepot)) # still 2 rows: idempotent
If charger simply appended (extend without clear), the second run would double the data: this is one of the most frequent bugs in production. Idempotent strategies are replacement (of a table or a partition, for example one day) and upsert (insert or update based on a key), covered in lesson 5.
Parameterize rather than hard-code
The processed date, the source path or the connection are parameters: executer_pipeline(date="2026-11-09", source=..., destination=...). You can then replay any day and test with fake data.
Key takeaways
- Extract without modifying, transform with pure functions, load idempotently.
- ETL and ELT differ in where the transformation happens.
- Explicit parameters; test that running twice changes nothing.
Exercises
Solutions and tests stay on the server. In the student space, your code is run and checked, and the tutor gives hints before the solution.
A mini idempotent pipeline
Complete
transformer(df)(without modifyingdf: cleanclientwith strip + lower, keep strictly positive amounts, then sum by client withas_index=False, sorted by client) andcharger(df, destination)(replace the contents of thedestinationlist with df’s records). The test code runs the pipeline twice.import io import pandas as pd def extraire(texte_csv): return pd.read_csv(io.StringIO(texte_csv)) def transformer(df): pass def charger(df, destination): pass source = 'client,montant\n Alice ,10\nbob,5\nalice,7\nbob,-2\n'ETL or ELT
Every night, a team loads the application’s raw tables into its cloud warehouse, then builds the analytical tables with SQL queries inside the warehouse. Which approach is this?
- ETL
- ELT
- Neither
- A manual export
Detect a non-idempotent load
Write
est_idempotent(etape, entree)that runsetape(entree, destination)twice on the same initially emptydestinationlist and returns True if its contents are identical after the first and the second run. Two load functions are provided for testing.def charger_ajout(lignes, destination): destination.extend(lignes) def charger_remplacement(lignes, destination): destination[:] = list(lignes) def est_idempotent(etape, entree): pass
End-of-lesson quiz
What does it mean for a pipeline to be idempotent?
- It is fast
- Rerunning it on the same data produces the same final state
- It only reads one source
What is a pure function?
- A function with no parameters
- A function that always returns the same result for the same input, with no side effects
- A function written in SQL
Why pass the processed date as a parameter?
- To be able to replay any day and to test
- To speed up the code
- Because pandas requires it
The rest of the course
- Extraction: CSV, JSON and SQL Databases18 min
- Transformation: Cleaning, Typing and Safe Joins20 min
- Data Quality Checks and Quarantine18 min
- Loading: Upsert, Transactions and Incremental Loads20 min
- Orchestration, Logging and Reliability18 min