Using PostgreSQL with Python (psycopg2) and Apache Airflow
Learn how to connect to PostgreSQL from Python with psycopg2, safely parameterize queries to prevent SQL injection, and orchestrate PostgreSQL tasks in an Airflow DAG.
PostgreSQL with Python and Airflow
Connecting from Python with psycopg2
import psycopg2
conn = psycopg2.connect(
host="localhost",
dbname="shop",
user="postgres",
password="mysecret"
)
cur = conn.cursor()
cur.execute("SELECT id, name, salary FROM employees WHERE department = %s", ("Engineering",))
rows = cur.fetchall()
for row in rows:
print(row)
cur.close()
conn.close()
Always Use Parameterized Queries
Never build SQL with plain string formatting/concatenation — it opens the door to SQL injection.
# DANGEROUS — never do this:
department = "Engineering"
cur.execute(f"SELECT * FROM employees WHERE department = '{department}'")
# SAFE — let psycopg2 handle escaping:
cur.execute("SELECT * FROM employees WHERE department = %s", (department,))
Inserting Data and Committing
cur.execute(
"INSERT INTO employees (name, email, salary) VALUES (%s, %s, %s)",
("Rahul Gupta", "rahul@gmail.com", 65000)
)
conn.commit() # nothing is saved until you commit
Using pandas for Bulk Reads
import pandas as pd
df = pd.read_sql("SELECT * FROM employees", conn)
print(df.describe())
Orchestrating with Apache Airflow
Airflow schedules and monitors data pipelines as a DAG (directed acyclic graph) of tasks — a natural fit for the incremental load and reconciliation patterns from earlier lessons, run on a schedule.
from airflow import DAG
from airflow.providers.postgres.operators.postgres import PostgresOperator
from datetime import datetime
with DAG(
dag_id="daily_sales_load",
schedule="0 2 * * *", # every day at 2 AM
start_date=datetime(2024, 1, 1),
catchup=False,
) as dag:
load_incremental = PostgresOperator(
task_id="load_incremental_orders",
postgres_conn_id="warehouse_db",
sql="""
INSERT INTO warehouse.orders
SELECT * FROM source.orders
WHERE updated_at > (SELECT last_loaded_at FROM load_watermark WHERE source_name = 'orders');
""",
)
update_watermark = PostgresOperator(
task_id="update_watermark",
postgres_conn_id="warehouse_db",
sql="UPDATE load_watermark SET last_loaded_at = now() WHERE source_name = 'orders';",
)
load_incremental >> update_watermark
load_incremental >> update_watermark declares the dependency: the watermark only advances after the load succeeds.
Advertisement728 x 90
Common Errors & Troubleshooting
psycopg2.OperationalError: could not connect to server— same root causes as any Postgres connection issue: wrong host/port, the server isn't accepting connections, or a firewall is blocking it — verify withpsqlusing the same credentials first.- Changes not showing up in the database — a very common beginner bug: forgetting to call
conn.commit()afterINSERT/UPDATE/DELETE— without it, the changes stay in an open, uncommitted transaction and are invisible to other connections. - Airflow DAG not appearing in the UI — check the DAG file has no syntax errors (Airflow silently skips broken DAG files in its scheduler logs) and that its
dag_idis unique across your Airflow instance.
Key Takeaways
- Always pass query parameters separately (
%splaceholders) rather than formatting them into the SQL string — this is the standard defense against SQL injection. - Nothing written through psycopg2 persists until you call
conn.commit(). - Airflow's
PostgresOperatorlets you schedule the exact SQL patterns from the Incremental Loads lesson as a reliable, monitored daily pipeline.
Advertisement320 x 100