Ultra-fast PostgreSQL driver for Python built with Rust - Binary protocol, prepared statements, thread-safe
This project has been archived.
The maintainers of this project have marked this project as archived. No new releases are expected.
Project description
🔥 PostPyro
Ultra-fast PostgreSQL driver for Python built with Rust
PostPyro combines the speed of Rust with the simplicity of Python for PostgreSQL database operations. Built on tokio-postgres and PyO3, it delivers exceptional performance while maintaining full DB-API 2.0 compatibility.
🚀 Why PostPyro?
- 🏎️ Blazing Fast: Rust-powered binary protocol communication
- 🔒 Type Safe: Comprehensive Python ↔ PostgreSQL type conversion
- 🧵 Thread Safe: Level 2 threadsafety for multi-threaded applications
- 📦 Zero Dependencies: Single wheel installation with no external deps
- 🎯 DB-API 2.0: Drop-in replacement for existing PostgreSQL drivers
- 🔧 Production Ready: Prepared statements, transactions, error handling
⚡ Installation
pip install PostPyro
That's it! No compilation, no system dependencies - just pure speed.
🎯 Quick Start
import PostPyro as pg
# Connect
conn = pg.Connection("postgresql://user:password@localhost:5432/mydb")
# Execute & Query
conn.execute("CREATE TABLE users (id SERIAL, name TEXT, age INTEGER)")
conn.execute("INSERT INTO users (name, age) VALUES ($1, $2)", ["Alice", 30])
# Fetch results
rows = conn.query("SELECT * FROM users WHERE age > $1", [25])
for row in rows:
print(f"{row['name']} is {row['age']} years old")
# Transactions
with conn.begin():
conn.execute("UPDATE users SET age = age + 1")
# Auto-commit on success, rollback on error
conn.close()
📊 Performance Comparison
| Driver | Simple Query | Parameterized | Bulk Insert | Threading |
|---|---|---|---|---|
| PostPyro | 🥇 0.12s | 🥉 0.12s | 🥉 147s | 4th |
| psycopg3 | 🥉 0.14s | 🥇 0.07s | 🥈 79s | 🥇 |
| psycopg2 | 🥈 0.12s | 🥈 0.07s | 🥇 67s | 🥈 |
| pg8000 | 4th | 4th | 4th | 🥉 |
PostPyro excels at simple queries and maintains competitive performance across all operations.
API Reference
Module Constants
pg.apilevel # "2.0" - DB-API 2.0 compliant
pg.threadsafety # 2 - Thread-safe connections
pg.paramstyle # "numeric" - Uses $1, $2, ... parameters
Module Functions
pg.connect(connection_string)
Create a new database connection using the connect function.
Parameters:
connection_string(str): PostgreSQL connection string- Format:
postgresql://user:password@host:port/database?options
- Format:
Returns: Connection object
Example:
conn = pg.connect("postgresql://user:pass@localhost:5432/mydb")
pg.Connection(connection_string)
Create a new database connection using the Connection class.
Parameters:
connection_string(str): PostgreSQL connection string
Returns: Connection object
Example:
conn = pg.Connection("postgresql://user:pass@localhost:5432/mydb")
pg.get_version()
Get the PostPyro driver version.
Returns: Version string (e.g., "0.1.2")
Example:
version = pg.get_version()
print(f"PostPyro version: {version}")
Connection Class Methods
conn.execute(query, params=None)
Execute INSERT, UPDATE, DELETE, or DDL statements.
Parameters:
query(str): SQL query stringparams(list, optional): Query parameters using $1, $2, ... placeholders
Returns: Number of rows affected (int)
Examples:
# INSERT
affected = conn.execute("INSERT INTO users (name, age) VALUES ($1, $2)", ["Alice", 30])
# UPDATE
affected = conn.execute("UPDATE users SET age = $1 WHERE name = $2", [31, "Alice"])
# DELETE
affected = conn.execute("DELETE FROM users WHERE age < $1", [18])
# DDL
conn.execute("CREATE TABLE products (id SERIAL PRIMARY KEY, name TEXT)")
conn.query(query, params=None)
Execute SELECT queries and return all matching rows.
Parameters:
query(str): SQL SELECT statementparams(list, optional): Query parameters
Returns: List of Row objects
Example:
rows = conn.query("SELECT id, name, age FROM users WHERE age > $1", [25])
for row in rows:
print(f"ID: {row['id']}, Name: {row['name']}, Age: {row['age']}")
conn.query_one(query, params=None)
Execute SELECT query and return exactly one row.
Parameters:
query(str): SQL SELECT statementparams(list, optional): Query parameters
Returns: Single Row object
Raises: Error if zero or multiple rows returned
Example:
user = conn.query_one("SELECT * FROM users WHERE id = $1", [1])
print(f"User name: {user['name']}")
conn.execute_batch(queries)
Execute multiple SQL statements in a batch for improved performance.
Parameters:
queries(list): List of SQL query strings
Returns: List of integers (rows affected for each query)
Example:
queries = [
"INSERT INTO users (name) VALUES ('Bob')",
"INSERT INTO users (name) VALUES ('Charlie')",
"INSERT INTO users (name) VALUES ('Diana')"
]
results = conn.execute_batch(queries)
print(f"Total rows inserted: {sum(results)}")
conn.prepare(query)
Prepare a SQL statement for repeated execution.
Parameters:
query(str): SQL statement to prepare
Returns: Statement identifier string
Example:
stmt_id = conn.prepare("SELECT * FROM users WHERE department = $1")
# Use with regular query methods
conn.begin()
Begin a new transaction and return a Transaction object.
Returns: Transaction object (context manager)
Example:
with conn.begin() as txn:
txn.execute("INSERT INTO users (name) VALUES ($1)", ["Alice"])
txn.execute("UPDATE accounts SET balance = balance - 100 WHERE user_id = $1", [1])
# Automatically commits on success, rolls back on exception
conn.ping()
Test if the connection is alive and responsive.
Returns: True if healthy, False if connection issues
Example:
if conn.ping():
print("✅ Connection is healthy")
else:
print("❌ Connection has issues")
conn.info()
Get detailed connection information and status.
Returns: Dictionary with connection details
Example:
info = conn.info()
print(f"Closed: {info['closed']}, Healthy: {info['healthy']}")
conn.close()
Close the database connection and free resources.
Example:
conn.close()
conn.is_closed()
Check if the connection has been closed.
Returns: True if closed, False if still open
Example:
if not conn.is_closed():
conn.query("SELECT 1") # Safe to use
Row Class
Represents a single row from a query result with dict-like interface.
Row Methods
Dict-like Access:
row = conn.query_one("SELECT id, name, email FROM users WHERE id = $1", [1])
# Access by column name
print(row['name'])
print(row['email'])
# Access by index
print(row[0]) # id
print(row[1]) # name
# Get with default
age = row.get('age', 0)
# Check length
print(f"Row has {len(row)} columns")
# Iterate over values
for value in row:
print(value)
# Get column names
columns = row.keys()
print(f"Columns: {list(columns)}")
# Get all values
values = row.values()
print(f"Values: {list(values)}")
# Get (column, value) pairs
for column, value in row.items():
print(f"{column}: {value}")
# Convert to dictionary
user_dict = row.to_dict()
Transaction Class
Represents a database transaction with automatic rollback on errors.
Transaction Methods
# Automatic transaction management
with conn.begin() as txn:
# Execute statements within transaction
txn.execute("INSERT INTO users (name) VALUES ($1)", ["Alice"])
txn.execute("UPDATE accounts SET balance = balance - 100 WHERE id = $1", [1])
# Query within transaction
users = txn.query("SELECT * FROM users WHERE created_today = true")
for user in users:
txn.execute("UPDATE users SET welcomed = true WHERE id = $1", [user['id']])
# Query single row within transaction
account = txn.query_one("SELECT balance FROM accounts WHERE id = $1", [1])
# Transaction commits automatically on successful exit
# OR rolls back automatically if exception occurs
Error Handling
PostPyro provides comprehensive PostgreSQL error mapping with specific exception types.
Exception Hierarchy
DatabaseError # Base database error
├── InterfaceError # Driver interface problems
├── DataError # Data processing errors
├── OperationalError # Database operation errors
├── IntegrityError # Constraint violations
├── InternalError # Internal database errors
├── ProgrammingError # SQL programming errors
└── NotSupportedError # Unsupported operations
Error Handling Examples
import PostPyro as pg
try:
conn = pg.Connection("postgresql://user:pass@localhost/db")
conn.execute("INSERT INTO users (email) VALUES ($1)", ["invalid-email"])
except pg.IntegrityError as e:
print(f"Constraint violation: {e}")
except pg.OperationalError as e:
print(f"Database operation failed: {e}")
except pg.ProgrammingError as e:
print(f"SQL syntax error: {e}")
except pg.DatabaseError as e:
print(f"General database error: {e}")
Type System
PostPyro automatically converts between Python and PostgreSQL types.
Supported Type Conversions
| PostgreSQL Type | Python Type | Example |
|---|---|---|
BOOLEAN |
bool |
True, False |
SMALLINT, INTEGER |
int |
42, -123 |
BIGINT |
int |
9223372036854775807 |
REAL, DOUBLE PRECISION |
float |
3.14, 2.718 |
TEXT, VARCHAR |
str |
"Hello World" |
BYTEA |
bytes |
b"binary data" |
DATE |
datetime.date |
date(2023, 12, 25) |
TIME |
datetime.time |
time(14, 30, 0) |
TIMESTAMP |
datetime.datetime |
datetime(2023, 12, 25, 14, 30) |
TIMESTAMPTZ |
datetime.datetime |
With timezone info |
UUID |
uuid.UUID |
UUID('550e8400-e29b-...') |
JSON, JSONB |
dict, list |
{"key": "value"}, [1, 2, 3] |
ARRAY |
list |
[1, 2, 3], ["a", "b", "c"] |
INET, CIDR |
str |
"192.168.1.1", "192.168.0.0/24" |
Type Usage Examples
from datetime import datetime, date
import uuid
# Insert various types
conn.execute("""
INSERT INTO mixed_types (
bool_col, int_col, float_col, text_col,
date_col, timestamp_col, uuid_col, json_col
) VALUES ($1, $2, $3, $4, $5, $6, $7, $8)
""", [
True, # boolean
42, # integer
3.14159, # float
"Hello PostgreSQL", # text
date(2023, 12, 25), # date
datetime(2023, 12, 25, 14, 30, 0), # timestamp
uuid.uuid4(), # uuid
{"name": "John", "scores": [85, 92, 78]} # json
])
# Query returns properly typed values
row = conn.query_one("SELECT * FROM mixed_types WHERE id = $1", [1])
assert isinstance(row['bool_col'], bool)
assert isinstance(row['json_col'], dict)
Execute a query and return all rows.
Parameters:
query(str): SQL query stringparams(list, optional): Query parameters
Returns: List of Row objects
Example:
rows = conn.query("SELECT * FROM users WHERE age > $1", [21])
for row in rows:
print(row['name'], row['age'])
Connection.query_one(query, params=None)
Execute a query and return exactly one row.
Parameters:
query(str): SQL query stringparams(list, optional): Query parameters
Returns: Single Row object
Raises: ProgrammingError if query returns 0 or multiple rows
Example:
user = conn.query_one("SELECT * FROM users WHERE id = $1", [1])
print(f"User: {user['name']}")
Connection.begin()
Begin a new transaction.
Returns: Transaction object
Example:
txn = conn.begin()
txn.execute("INSERT INTO logs (message) VALUES ($1)", ["Started process"])
txn.commit()
Connection.close()
Close the database connection.
Example:
conn.close()
Row
Represents a single row from a query result.
Row Access
row = conn.query_one("SELECT id, name FROM users WHERE id = 1")
# Access by column name
print(row['id'], row['name'])
# Access by column index
print(row[0], row[1])
# Get with default value
age = row.get('age', 0)
# Iterate over values
for value in row:
print(value)
# Get column names
columns = row.keys()
# Convert to dictionary
user_dict = row.to_dict()
Transaction
Represents a database transaction.
Transaction.execute(query, params=None)
Execute a query within the transaction.
Transaction.query(query, params=None)
Query within the transaction.
Transaction.query_one(query, params=None)
Query one row within the transaction.
Transaction.commit()
Commit the transaction.
Transaction.rollback()
Roll back the transaction.
Transaction.savepoint(name)
Create a savepoint.
Parameters:
name(str): Savepoint name
Transaction.rollback_to(name)
Roll back to a savepoint.
Parameters:
name(str): Savepoint name
Example:
with conn.begin() as txn:
txn.execute("INSERT INTO users (name) VALUES ($1)", ["Alice"])
txn.savepoint("after_insert")
try:
txn.execute("INSERT INTO users (name) VALUES ($1)", ["Bob"])
# Some validation...
except:
txn.rollback_to("after_insert") # Undo the second insert
txn.commit() # Commit only Alice
Data Types
pypg-driver supports comprehensive type conversion between Python and PostgreSQL:
| PostgreSQL Type | Python Type | Example |
|---|---|---|
| INTEGER/SMALLINT/BIGINT | int | 42 |
| REAL/DOUBLE PRECISION | float | 3.14 |
| TEXT/VARCHAR | str | "hello" |
| BYTEA | bytes | b"data" |
| BOOLEAN | bool | True |
| DATE | datetime.date | date(2023, 12, 25) |
| TIME | datetime.time | time(14, 30, 0) |
| TIMESTAMP | datetime.datetime | datetime(2023, 12, 25, 14, 30, 0) |
| TIMESTAMPTZ | datetime.datetime | datetime(2023, 12, 25, 14, 30, 0, tzinfo=timezone.utc) |
| UUID | uuid.UUID | uuid.uuid4() |
| JSON/JSONB | dict/list | {"key": "value"} |
| Arrays | list | [1, 2, 3] |
Error Handling
pypg-driver raises DB-API 2.0 compliant exceptions:
DatabaseError: Base exception for all database errorsInterfaceError: Client-side errors (connection issues)DataError: Data processing errors (type conversion)OperationalError: Database operational errorsIntegrityError: Constraint violationsInternalError: Database internal errorsProgrammingError: SQL syntax errors, wrong parametersNotSupportedError: Unsupported operations
Example:
try:
conn.execute("INVALID SQL")
except pg.ProgrammingError as e:
print(f"SQL Error: {e}")
except pg.InterfaceError as e:
print(f"Connection Error: {e}")
Transactions
Transactions provide ACID properties for database operations:
# Manual transaction management
txn = conn.begin()
try:
txn.execute("INSERT INTO accounts (name, balance) VALUES ($1, $2)", ["Alice", 1000])
txn.execute("INSERT INTO accounts (name, balance) VALUES ($1, $2)", ["Bob", 1000])
txn.commit()
except Exception:
txn.rollback()
raise
# Context manager (auto-rollback on exception)
with conn.begin() as txn:
txn.execute("UPDATE accounts SET balance = balance - 100 WHERE name = $1", ["Alice"])
txn.execute("UPDATE accounts SET balance = balance + 100 WHERE name = $1", ["Bob"])
# Automatic commit on success, rollback on exception
Performance
pypg-driver is designed for high performance:
- Rust Backend: Compiled Rust code for maximum speed
- Zero-Copy: Efficient data transfer between Python and Rust
- Async I/O: Non-blocking database operations
- Connection Reuse: Keep connections open for multiple operations
- Prepared Statements: Cache query plans for repeated execution
Development
Building from Source
# Install Rust
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
# Clone and build
git clone https://github.com/magi8101/pypg-driver.git
cd pypg-driver
pip install -e .
Running Tests
# Install test dependencies
pip install pytest
# Start PostgreSQL test instance (using Docker)
docker run -d --name postgres-test -e POSTGRES_PASSWORD=test -p 5432:5432 postgres:15
# Run tests
pytest tests/
License
MIT License - see LICENSE file for details.
Acknowledgments
- tokio-postgres for the async PostgreSQL driver
- PyO3 for Python-Rust bindings
Project details
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distributions
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file PostPyro-1.0.0-cp38-abi3-win_amd64.whl.
File metadata
- Download URL: PostPyro-1.0.0-cp38-abi3-win_amd64.whl
- Upload date:
- Size: 565.0 kB
- Tags: CPython 3.8+, Windows x86-64
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.13.5
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
870b2b6eb20598edbf42ba22fe666f942f58fdf1aa76c5128513ff41a83654e4
|
|
| MD5 |
8568d6976f39648dd5b91a1c6f098cbd
|
|
| BLAKE2b-256 |
c1b4a1a4e8a104959f8d73b56ea06555726299a18ee8ee9eff128c783b73e729
|