Skip to main content

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

PyPI version Python versions License

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

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 string
  • params (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 statement
  • params (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 statement
  • params (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 string
  • params (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 string
  • params (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 errors
  • InterfaceError: Client-side errors (connection issues)
  • DataError: Data processing errors (type conversion)
  • OperationalError: Database operational errors
  • IntegrityError: Constraint violations
  • InternalError: Database internal errors
  • ProgrammingError: SQL syntax errors, wrong parameters
  • NotSupportedError: 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

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

No source distribution files available for this release.See tutorial on generating distribution archives.

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

PostPyro-1.0.0-cp38-abi3-win_amd64.whl (565.0 kB view details)

Uploaded CPython 3.8+Windows x86-64

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

Hashes for PostPyro-1.0.0-cp38-abi3-win_amd64.whl
Algorithm Hash digest
SHA256 870b2b6eb20598edbf42ba22fe666f942f58fdf1aa76c5128513ff41a83654e4
MD5 8568d6976f39648dd5b91a1c6f098cbd
BLAKE2b-256 c1b4a1a4e8a104959f8d73b56ea06555726299a18ee8ee9eff128c783b73e729

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page