This directory contains example code showing how to use pgoutput-decoder in different scenarios.
All examples assume you have:
- PostgreSQL 12+ with
wal_level=logical - A database with some tables
- A publication created:
CREATE PUBLICATION my_pub FOR ALL TABLES;
Start here! Simple async CDC consumer using async for.
python examples/basic_cdc.pyBest for: Learning the basics, async applications, modern Python code
If you have an existing synchronous application and can't easily convert to async:
Simple wrapper using asyncio.run() for synchronous scripts.
python examples/sync_wrapper.pyBest for:
- Simple scripts and CLI tools
- Batch processing jobs
- Applications that can block on CDC messages
- Learning how to integrate CDC into sync code
Pros: Dead simple, just wrap in a function Cons: Blocks the entire process
Background thread pattern for long-running sync applications.
python examples/background_thread.pyBest for:
- Flask, Django, or other web frameworks
- Long-running services with existing sync code
- Applications that need to do other work while monitoring CDC
- Microservices needing CDC as a side-channel
Pros:
- Integrates cleanly with sync code
- Thread-safe message queue
- Natural backpressure handling
- Non-blocking message retrieval
Cons:
- Threading overhead
- Need careful error handling
This is the recommended pattern for most sync applications.
Dispatch CDC messages to Celery tasks for distributed processing.
# Terminal 1: Start Celery worker
celery -A examples.celery_integration worker --loglevel=info
# Terminal 2: Run CDC consumer
python examples/celery_integration.pyBest for:
- Distributed systems
- Horizontal scaling
- Long-running message processing
- Fault-tolerant workflows
Prerequisites: Redis or RabbitMQ
Django management command for CDC processing.
python manage.py consume_cdc --max-messages=10Best for:
- Django projects
- Running as a separate process/container
- Integration with Django ORM
Usage: Copy to your_app/management/commands/consume_cdc.py
┌─────────────────────────────────────────────────────────────┐
│ Starting a NEW project? │
│ └─> Use basic_cdc.py (async) │
└─────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────┐
│ Have EXISTING sync code (Flask, Django, legacy)? │
│ └─> Use background_thread.py │
└─────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────┐
│ Need DISTRIBUTED processing (multiple workers)? │
│ └─> Use celery_integration.py │
└─────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────┐
│ Simple SCRIPT that processes N messages then exits? │
│ └─> Use sync_wrapper.py │
└─────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────┐
│ Django project? │
│ └─> Use django_command.py │
└─────────────────────────────────────────────────────────────┘
# See sync_wrapper.py, example_simple()
consume_cdc_messages(
...,
callback=my_handler,
max_messages=100 # Stop after 100 messages
)# See basic_cdc.py or sync_wrapper.py, example_continuous()
try:
async for message in reader: # or consume_cdc_messages(..., max_messages=None)
process(message)
except KeyboardInterrupt:
print("Stopping...")def handle_message(message):
table = get_table_name(message)
if table == "public.orders":
handle_order_change(message)
elif table == "public.customers":
handle_customer_change(message)def handle_message(message):
if message.op == "c": # INSERT
handle_create(message.after)
elif message.op == "u": # UPDATE
handle_update(message.before, message.after)
elif message.op == "d": # DELETE
handle_delete(message.before)# docker-compose.yml
version: '3.8'
services:
postgres:
image: postgres:18.1-alpine
environment:
POSTGRES_USER: postgres
POSTGRES_PASSWORD: password
POSTGRES_DB: ecommerce_db
ports:
- "5432:5432"
command:
- postgres
- -c
- wal_level=logical
- -c
- max_replication_slots=4
- -c
- max_wal_senders=4
# Start it
docker-compose up -d-- Connect to the database
psql -h localhost -U postgres -d ecommerce_db
-- Create tables
CREATE TABLE customers (
id SERIAL PRIMARY KEY,
name TEXT NOT NULL,
email TEXT UNIQUE NOT NULL
);
CREATE TABLE orders (
id SERIAL PRIMARY KEY,
customer_id INTEGER REFERENCES customers(id),
total DECIMAL(10,2),
created_at TIMESTAMP DEFAULT NOW()
);
-- Create publication
CREATE PUBLICATION ecommerce_pub FOR ALL TABLES;
-- Verify
SELECT * FROM pg_publication;# In one terminal, run an example
python examples/basic_cdc.py
# In another terminal, make some changes
psql -h localhost -U postgres -d ecommerce_db
INSERT INTO customers (name, email) VALUES ('Alice', 'alice@example.com');
INSERT INTO orders (customer_id, total) VALUES (1, 99.99);
UPDATE customers SET name = 'Alice Smith' WHERE id = 1;
DELETE FROM orders WHERE id = 1;You should see CDC messages appear in the first terminal!
# Install in development mode
uv sync
uv run maturin develop
# Or install from PyPI
pip install pgoutput-decoder# Check PostgreSQL is running
docker ps
# Check connection settings
psql -h localhost -U postgres -d ecommerce_db
# Verify wal_level
psql -c "SHOW wal_level;" # Should be 'logical'-- Create it
CREATE PUBLICATION my_pub FOR ALL TABLES;
-- Or for specific tables
CREATE PUBLICATION my_pub FOR TABLE customers, orders;# Use a unique slot name per consumer
LogicalReplicationReader(
slot_name="unique_slot_name_123", # Make this unique
...
)-- Or drop existing slot
SELECT pg_drop_replication_slot('slot_name');- Read the main README for more details
- Check out the API documentation
- See test files for more usage examples
- Join discussions on GitHub for help
Found a bug or want to add an example? PRs welcome!
- Fork the repo
- Create a feature branch
- Add your example with clear documentation
- Submit a PR
Please ensure examples:
- Have clear docstrings
- Include inline comments for beginners
- Show error handling
- Demonstrate best practices