psycopg2
The most widely used PostgreSQL adapter for Python. Sync only. Two packages on PyPI: psycopg2 (source, requires libpq dev headers) and psycopg2-binary (pre-compiled, no system deps). Official docs explicitly warn against psycopg2-binary in production. Current version: 2.9.11 (Mar 2026). For async PostgreSQL use asyncpg or psycopg (v3). For new projects consider migrating to psycopg (v3) which has both sync and async.
Warnings
- gotcha psycopg2-binary is NOT recommended for production. Official docs: 'The binary package is a practical choice for development and testing but in production it is advised to use the package built from source.' It bundles its own libpq which can conflict with system libraries.
- gotcha Parameter placeholder is %s for ALL types — not ? (sqlite3) or :param (SQLAlchemy). Using ? raises ProgrammingError. Using f-strings creates SQL injection risk.
- gotcha Single-value parameterized queries need a tuple: cur.execute('SELECT %s', (value,)) — not cur.execute('SELECT %s', value). Passing a non-tuple raises TypeError or treats string as iterable of chars.
- gotcha Default cursor returns rows as tuples. Accessing by column name raises TypeError. Use RealDictCursor or DictCursor from psycopg2.extras for dict access.
- gotcha psycopg2 is sync only. Cannot be used in async contexts (FastAPI, asyncio) without blocking the event loop. Use asyncpg or psycopg (v3) for async.
- gotcha psycopg2 ≠ psycopg (v3). They are different packages with different APIs. psycopg (without the 2) is the newer version — different install, different import patterns.
- gotcha Connection is not thread-safe. Do not share a single psycopg2 connection across threads. Use a connection pool (psycopg2.pool or SQLAlchemy's pool).
Install
-
pip install psycopg2-binary -
pip install psycopg2
Imports
- connect
import psycopg2 conn = psycopg2.connect( host='localhost', port=5432, dbname='mydb', user='myuser', password='mypassword' ) cur = conn.cursor() cur.execute('SELECT * FROM users WHERE id = %s', (user_id,)) # parameterized rows = cur.fetchall() cur.close() conn.close() - RealDictCursor
import psycopg2 import psycopg2.extras conn = psycopg2.connect(...) cur = conn.cursor(cursor_factory=psycopg2.extras.RealDictCursor) cur.execute('SELECT id, name FROM users') rows = cur.fetchall() print(rows[0]['name']) # dict access not index
Quickstart
# Development: pip install psycopg2-binary
# Production: pip install psycopg2 (requires libpq-dev)
import psycopg2
import psycopg2.extras
conn = psycopg2.connect(
host='localhost',
dbname='mydb',
user='myuser',
password='mypassword'
)
with conn:
with conn.cursor(cursor_factory=psycopg2.extras.RealDictCursor) as cur:
# Parameterized query — always use %s
cur.execute(
'SELECT * FROM users WHERE active = %s',
(True,) # note: tuple, not single value
)
users = cur.fetchall()
for user in users:
print(user['name'])
conn.close()